- 자바스크립트에 타입을 부여한 언어
- 자바스크립트와 달리 브라우저에서 실행하려면 컴파일이 필요하다.
- 에러 사전 방지
// math.js function sum(a, b) { return a + b; }// math.ts function sum(a: number, b: number) { return a + b; }: 다음과 같이 타입을 지정해 에러를 사전에 방지할 수 있다.
- 코드 가이드 및 자동 완성 (개발 생산성 향상)
: VSCode에서 타입에 대한 API를 미리 보기로 띄워줄 수 있음
API를 다 일일이 치는 것이 아니라 tab으로 빠르고 정확하게 작성 가능
- 기본 타입의 종류
Boolean, Number, String, Object, Array, Tuple, Enum, Any, Void, Null, Undefined, Never
- Tuple
배열의 길이가 고정되고 각 요소의 타입이 지정되어 있는 배열 형식let arr: [string, number] = ['hi', 10];
- Enum
C, Java와 같은 다른 언어에서 흔하게 쓰이는 타입, 특정 값(상수)들의 집합enum Avengers { Capt, IronMan, Thor } let hero: Avengers = Avengers.Capt;
- Any
단어 의미 그대로 모든 타입에 대해서 허용let str: any = 'hi'; let num: any = 10; let arr: any = ['a', 2, true];- Void
변수에는undefined와null만 할당하고, 함수에는 반환 값을 설정할 수 없는 타입let unuseful: void = undefined; function notuse(): void { console.log('sth'); }
- Never
함수의 끝에 절대 도달하지 않는다는 의미를 지닌 타입// 이 함수는 절대 함수의 끝까지 실행되지 않는다는 의미 function neverEnd(): never { while (true) { } }
function sum(a: number, b: number): number { return a + b; }기존 자바스크립트 함수의 선언 방식에서 매개변수와 함수의 반환 값에 타입을 추가
++ 함수의 반환 값에 타입을 정하지 않을 때는void라도 사용
- 타입스크립트에서는 함수의 인자를 모두 필수 값으로 간주한다
- 정의된 매개변수 값만 받을 수 있고 추가로 인자를 받을 수 없다
function sum(a: number, b: number): number { return a + b; } sum(10, 20); // 30 sum(10, 20, 30); // error, too many parameters sum(10); // error, too few parameters
- 자바스크립트의 this가 잘못 사용되었을 때 감지 가능
- 상호 간에 정의한 약속 혹은 규칙을 의미
- 보통 아래와 같은 범주에 대해 약속을 정의할 수 있다
- 객체의 스펙(속성과 속성의 타입)
- 함수의 파라미터
- 함수의 스펙(파라미터, 반환 타입 등)
- 배열과 객체를 접근하는 방식
- 클래스
let person = { name: 'Capt', age: 28 }; function logAge(obj: { age: number }) { console.log(obj.age); // 28 } logAge(person); // 28logAge() 함수에서 받는 인자의 형태는 age를 속성으로 갖는 객체
여기에 인터페이스를 적용할 경우 다음과 같다interface personAge { age: number; } function logAge(obj: personAge) { console.log(obj.age); } let person = { name: 'Capt', age: 28 }; logAge(person);
logAge()의 인자는personAge라는 타입을 가져야한다
인터페이스에 정의된 속성, 타입의 조건만 만족한다면 객체의 속성 갯수가 더 많아도 상관 없다
인터페이스에 선언된 속성 순서를 지키지 않아도 된다
interface 인터페이스_이름 { 속성?: 타입; }이처럼 속성의 끝에 ?를 붙인 예시를 작성하면 아래와 같다
interface CraftBeer { name: string; hop?: number; } let myBeer = { name: 'Saporo' }; function brewBeer(beer: CraftBeer) { console.log(beer.name); // Saporo } brewBeer(myBeer);
brewBeer()함수에서Beer인터페이스를 인자의 타입으로 선언했음에도 불구하고, 인자로 넘긴 객체에는hop속성이 없다.hop을 옵션 속성으로 선언했기 때문
- 인터페이스를 사용할 때 속성을 선택적으로 적용 가능
- 인터페이스에 정의되어 있지 않은 속성에 대해서 인지시켜줄 수 있음
- 인터페이스로 객체를 처음 생성할 때만 값을 할당하고 그 이후에는 변경할 수 없는 속성
interface CraftBeer { readonly brand: string; }
- 배열을 선언할 때
ReadonlyArray<T>타입을 사용하면 읽기 전용 배열 생성 가능let arr: ReadonlyArray<number> = [1,2,3]; arr.splice(0,1); // error arr.push(4); // error arr[0] = 100; // error
- 함수의 타입을 정의할 때에도 사용
함수의 인자의 타입과 반환 값의 타입을 정함interface login { (username: string, password: string): boolean; }let loginUser: login; loginUser = function(id: string, pw: string) { console.log('로그인 했습니다'); return true; }
- C#이나 자바처럼 타입스크립트에서도 클래스가 일정 조건을 만족하도록 타입 규칙을 정할 수 있다
interface CraftBeer { beerName: string; nameBeer(beer: string): void; } class myBeer implements CraftBeer { beerName: string = 'Baby Guinness'; nameBeer(b: string) { this.beerName = b; } constructor() {} }
- 클래스와 마찬가지로 인터페이스도 인터페이스 간 확장이 가능
혹은 아래와 같이 여러 인터페이스를 상속받아 사용 가능함interface Person { name: string; } interface Developer extends Person { skill: string; } let fe = {} as Developer; fe.name = 'josh'; fe.skill = 'TypeScript';interface Person { name: string; } interface Drinker { drink: string; } interface Developer extends Person { skill: string; } let fe = {} as Developer; fe.name = 'josh'; fe.skill = 'TypeScript'; fe.drink = 'Beer';
- 특정 값들의 집합을 의미하는 자료형
enum Direction { Up = 1, Down, Left, Right }위와 같이 숫자형 이넘을 선언할 때 초기 값을 주면 초기 값부터 차례로 1씩 증가
Up - 1 Down - 2 Left - 3 Right - 4만약 아래와 같이 초기 값을 주지 않으면 0부터 차례로 1씩 증가
enum Direction { Up, // 0 Down, // 1 Left, // 2 Right // 3 }
- 숫자형 이넘과 흡사하지만 런타임에서의 미세한 차이가 있음
- 이넘 값 전부 다 특정 문자 또는 다른 이넘 값으로 초기화 해줘야 한다
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT", }
- 이넘에 문자와 숫자를 혼합하여 생성할 수는 있지만 권장하지 않음
enum BooleanLikeHeterogeneousEnum { No = 0, Yes = "YES", }
- 자바스크립트의 OR 연산자(||)와 같이 A이거나 B이다 라는 의미의 타입
function logText(text: string | number) { // ... }|연산자를 이용하여 타입을 여러 개 연결하는 방식
- 여러 타입을 모두 만족하는 하나의 타입
Capt는 아래와 같이 정의됨interface Person { name: string; age: number; } interface Developer { name: string; skill: number; } type Capt = Person & Developer;{ name: string; age: number; skill: string; }
- 클래스의 속성에
readonly키워드를 사용하면 아래와 같이 접근만 가능class Developer { readonly name: string; constructor(theName: string) { this.name = theName; } } let john = new Developer("John"); john.name = "John"; // error! name is readonly.readonly를 사용하면constructor()함수에 초기 값 설정 로직을 넣어줘야 하므로 다음과 같이 인자에readonly키워드를 추가해서 코드를 줄일 수 있다class Developer { readonly name: string; constructor(readonly name: string) { } }
- 인터페이스와 비슷한 역할을 하면서도 조금 다르다
- 추상 클래스는 특정 클래스의 상속 대상이 되는 클래스이며 좀 더 상위 레벨에서 속성, 메서드의 모양을 정의한다
abstract class Developer { abstract coding(): void; // 'abstract'가 붙으면 상속 받은 클래스에서 무조건 구현해야 함 drink(): void { console.log('drink sth'); } } class FrontEndDeveloper extends Developer { coding(): void { // Developer 클래스를 상속 받은 클래스에서 무조건 정의해야 하는 메서드 console.log('develop web'); } design(): void { console.log('design web'); } } const dev = new Developer(); // error: cannot create an instance of an abstract class const josh = new FrontEndDeveloper(); josh.coding(); // develop web josh.drink(); // drink sth josh.design(); // design web
- 제네릭은 C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징
- 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용
- 제네릭이란 타입을 마치 함수의 파라미터처럼 사용하는 것
위 함수는 제네릭 기본 문법이 적용된 형태. 함수를 호출할 때 아래와 같이 함수 안에서 사용할 타입을 넘겨줄 수 있다.function getText<T>(text: T): T { return text; }위 코드 중getText<string>('hi'); getText<number>(10); getText<boolean>(true);getText<string>('hi')를 호출 했을 때 함수에서 제네릭이 아래와 같이 동작제네릭 타입이function getText<string>(text: T): T { return text; }<string>이 되는 이유는getText()함수를 호출할 때 제네릭(함수에서 사용할 타입) 값으로string을 넘겼기 때문함수의 인자로 hi 라는 값을 아래와 같이 넘기게 되면getText<string>();getText<string>('hi');getText함수는 아래와 같이 타입을 정의한 것과 같다위 함수는 입력 값의 타입이function getText<string>(text: string): string { return text; }string이면서 반환 값 타입도string이어야 한다
- 함수의 인자로 어떤 타입이 들어갔고 어떤 값이 반환되는지는 알 수 있게 하기 위함이다
- 타입스크립트가 코드를 해석해 나가는 동작
let x = 3;위와 같이
x에 대한 타입을 따로 지정하지 않더라도 일단x는number로 간주된다. 이렇게 변수를 선언하거나 초기화 할 때 타입이 추론된다. (++ 변수, 속성, 인자의 기본 값, 함수의 반환 값 등을 설정할 때)
- 몇 개의 표현식(코드)을 바탕으로 타입을 추론하는데, 그 표현식을 이용하여 추론된 가장 근접한 타입을 Best Common Type이라고 한다.
- 코드의 위치(문맥)를 기준으로 타입을 추론하는 방식
window.onmousedown = function(mouseEvent) { console.log(mouseEvent.button); //<- OK console.log(mouseEvent.kangaroo); //<- Error! };위 코드를 타입스크립트 검사기 관점에서 보면
window.onmousedown에 할당되는 함수의 타입을 추론하기 위해window.onmousedown타입을 검사한다. 타입 검사가 끝나고 나면 함수의 타입이 마우스 이벤트와 연관이 있다고 추론하기 때문에mouseEvent인자에button속성은 있지만kangaroo속성은 없다고 결론을 내린다.
- 타입 체크는 값의 형태에 기반하여 이루어져야 한다. 이걸 Duck Typing 또는 Structural Subtyping 이라고 한다.
- Duck Typing : 객체의 변수 및 메서드의 집합이 객체의 타입을 결정하는 것을 의미. 동적 타이핑의 한 종류
- Structural Subtyping : 객체의 실제 구조나 정의에 따라 타입을 결정하는 것을 의미
- 타입스크립트 코드에서 특정 타입이 다른 타입에 잘 맞는지를 의미
C#이나 Java였다면 위 코드에서 에러가 날 것이다.interface Ironman { name: string; } class Avengers { name: string; } let i: Ironman; i = new Avengers(); // OK, because of structural typingAvengers클래스가 명시적으로Ironman인터페이스를 상속받아 구현하지 않았기 때문이다. 위와 같은 코드가 타입스크립트에서 정상적으로 동작하는 이유는 자바스크립트의 작동 방식과 관련이 있다. 기본적으로 자바스크립트는 객체 리터럴이나 익명 함수 등을 사용하기 때문에 명시적으로 타입을 지정하는 것보다는 코드의 구조 관점에서 타입을 지정하는 것이 더 잘 어울린다.
- 타입스크립트는 컴파일 시점에 타입을 추론할 수 없는 특정 타입에 대해서 일단 안전하다고 보는 특성이 있다. 이걸 "들리지 않는다(it is said to not be sound)"라고 표현한다.
- 특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미
위와 같이 string, number와 같은 간단한 타입 뿐만 아니라 interface 레벨의 복잡한 타입에도 별칭을 부여할 수 있다.// string 타입을 사용할 때 const name: string = 'capt'; // 타입 별칭을 사용할 때 type MyName = string; const name: MyName = 'capt';제네릭도 사용 가능하다.type Developer = { name: string; skill: string; }type User<T> = { name: T }
- 타입 별칭은 새로운 타입 값을 하나 생성하는 것이 아니라 정의한 타입에 대해 나중에 쉽게 참고할 수 있게 이름을 부여하는 것
- 타입 별칭과 인터페이스의 가장 큰 차이점은 타입의 확장 가능 / 불가능 여부
- 인터페이스는 확장이 가능한데 반해 타입 별칭은 확장이 불가능
- 따라서, 가능한한 type 보다는 interface로 선언해서 사용하는 것을 추천
- 타입스크립트 선언 파일
d.ts는 타입스크립트 코드의 타입 추론을 돕는 파일. 전역 변수로 선언한 변수를 특정 파일에서import구문 없이 사용하는 경우 해당 변수를 인식하지 못한다. 그럴 때 아래와 같이 해당 변수를 선언해서 에러가 나지 않게 할 수 있다.declare const global = 'sth';
- 타입스크립트 파일에서 사용할 순 있지만 선언되어 있지 않은 전역 변수나 전역 함수는 아래와 같이 타입을 선언할 수 있다.
// 전역 변수 declare const pi = 3.14; // 전역 함수 declare namespace myLib { function greet(person: string): string; let name: string; } myLib.greet('캡틴'); myLib.name = '타노스';
- 이미 정의해 놓은 타입을 변환할 때 사용하기 좋은 타입 문법
- 유틸리티 타입을 쓰면 훨씬 더 간결한 문법으로 타입을 정의할 수 있다.
- 파셜(Partial) 타입은 특정 타입의 부분 집합을 만족하는 타입을 정의
interface Address { email: string; address: string; } type MayHaveEmail = Partial<Address>; const me: MayHaveEmail = {}; // 가능 const you: MayHaveEmail = { email: 'test@abc.com' }; // 가능 const all: MayHaveEmail = { email: 'capt@hero.com', address: 'Pangyo' }; // 가능
- 픽(Pick) 타입은 특정 타입에서 몇 개의 속성을 선택(pick)하여 타입을 정의
interface Hero { name: string; skill: string; } const human: Pick<Hero, 'name'> = { name: '스킬이 없는 사람', };type HasThen<T> = Pick<Promise<T>, 'then' | 'catch'>; let hasThen: HasThen<number> = Promise.resolve(4); hasThen.th // 위에서 'then'만 선택하면 'then'만 제공, 'catch' 선택하면 'catch만 제공'
- 오밋(Omit) 타입은 특정 타입에서 지정된 속성만 제거한 타입을 정의
interface AddressBook { name: string; phone: number; address: string; company: string; } const phoneBook: Omit<AddressBook, 'address'> = { name: '재택근무', phone: 12342223333, company: '내 방' } const chingtao: Omit<AddressBook, 'address'|'company'> = { name: '중국집', phone: 44455557777 }
- 기존에 정의되어 있는 타입을 새로운 타입으로 변환해 주는 문법
- 자바스크립트
map()API 함수를 타입에 적용한 것과 같은 효과
- 자바스크립트의 map 함수를 타입에 적용했다고 보면 된다.
{ [ P in K ] : T } { [ P in K ] ? : T } { readonly [ P in K ] : T } { readonly [ P in K ] ? : T }