개발자로서 일을 시작하면서 타입스크립트를 실무에서 사용한 지 1년 반 정도가 되었다. 기본적인 문법과 타입에 대해서 공부하고 그때 그때 부족한 부분을 채우면서 사용을 했었는데, 『타입스크립트 교과서』 라는 실전에 초점을 맞힌 책이 출간되어서 정리해 보려고 한다.
이전에 몰랐던 내용이나 알고 있었지만 중요하다고 생각되는 부분들만 정리해서 작성하였다.
type {}
는 null
과 undefined
를 제외한 모든 타입을 의미한다.
(빈 interface도 동일하게 동작)
let a = null; //let a: any
let b = undefined; //let b: any
let
으로 선언한 변수에 null
이나 undefined
를 할당하면 any
타입으로 추론한다.
튜플 타입은 push
, pop
, shift
, unshift
메서드 사용이 가능하다.
튜플 타입에 spread 문법 사용 가능하다.
sperad syntax, 구조 분해 할당을 해도 타입 추론이 가능하다.
튜플 타입에도 ?
(옵셔널) 수식어를 사용할 수 있다.
let tuple: [number, boolean?, string?] = [1, false, 'hi'];
class
는 타입으로도 사용이 가능하다.
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
const person: Person = new Person('Jonn');
any[]
)에 요소를 추가하면 타입이 바뀌지만 제거한다고 해서 이전 타입으로 돌아가지 않는다.const arr = []; //const arr: any[]
arr.push('1');
arr; //const arr: string[]
arr.pop();
arr; //const arr: string[]
keyof any
는 number | string | symbol
이다.fetch
, JSON.parse
는 명시적으로 any
를 반환한다.
fetch('url')
.then((response) => response.json())
.then((result) => { //(parameter) reult: any
});
const result = JSON.parse('{"hello":"json"}'); //const result: any
() => void
타입은 반환값이 달라도 무시한다.
interface는 재선언을 통한 선언 병합이 가능하다
interface Merge {
one: string
}
interface Merge {
two: number;
}
const example: Merge = {
one: '1',
twp: 2.
};
namespace
내부에 타입을 선언할 수 있고 export
를 사용하여 외부에서 사용할 수 있다.namespace Example {
export interface Inner {
test: string;
}
export type InnerTwo = number;
}
const ex1: Example.Inner = {
test: 'hello'
}
const ex2: Example.InnerTwo = 123;
namespace
도 이름이 같다면 interface
와 마찬가지로 병합된다.
namespace
를 중첩해서 사용할 수 있고, 내부 namespace
를 export
해야 한다.
namespace
내부에 실제 값을 선언했다면 namespace
자체를 자바스크립트 값으로 사용할 수도 있다.
namespace
를 declare
로 선언하면 이미 다른 곳에 실제 값이 있다고 생각해서 내부 구현부를 생략할 수 있다.
수식어 앞에 -를 붙이면(ex) -readonly) 해당 수식어를 제거된 채로 속성을 가져올 수 있다.
interface Original {
readonly name?: string
}
type Copy = {
-readonly [key in keyof Original]-?: Original[key];
}
type Start = string | number;
type Result<Key> = Key extends string ? Key[] : never;
let n: Result<Start> = ['hi']; //let n: string[]
//Result<string | number> => Result<string> | Result<number> => string[] | never => string[]
[T]
)로 분배 법칙이 일어나지 않는다.function example1(a: string, ...b: number[]) {}
example('hi', 123, 4, 56);
this
의 타입은 매개변수의 첫 번째 자리에 표기하면 되고 다른 매개 변수들은 한 자리씩 뒤로 밀려난다.function example(this: Window) {
console.log(this);
}
메서드를 갖고 있는 객체는 this
가 자기 자신으로 추론되기 때문에 this
가 바뀌는 경우를 제외하고 명시적으로 타이핑하지 않아도 된다.
클래스나 인터페이스의 메서드는 this
를 타입으로 사용할 수 있다.
여러 오버로딩에 동시에 해당될 수 있는 경우는 제일 먼저 선언된 오버로딩이 적용되므로 오버로딩의 순서는 좁은 타입에서 넓은 타입 순으로 해야 한다.
콜백 함수의 매개변수는 생략 가능하다.
콜백 함수의 반환값이 void일 때 어떠한 반환 값이 와도 상관 없다(ex) Array.forEach
)
특성 | 설명 |
---|---|
공변성 | A->B 일때 T<A>->T<B> |
반공변성 | A->B 일때 T<B>->T<A> |
이변성 | A->B 일때 T<A>->T<B> 이면서 T<B>->T<A> |
무공변성 | A->B 일때 T<A>->T<B> , T<B>->T<A> 모두 안되는 경우 |
함수<반환값>
으로 표현 시 a->b
일때 T<a> ->T<b>
이므로 함수의 반환값은 공변성을 갖는다.(strict 옵션 관계없이 항상)
매개변수<반환값>
으로 표현 시 b->a
일때 T<a> ->T<b>
이므로 함수의 매개변수는 반공병성을 갖는다.(strict 옵션 해제 시 이변성)
함수(매개변수): 반환값
으로 선언시 매개변수가 이변성을 가지고 함수:(매개변수) => 반환값
으로 선언 시 매개변수가 반공변성을 가진다.
타입스크립트는 생성자 함수 방식으로 개체를 만드는 것을 지원하지 않는다.(new
를 붙여서 호출할 수 있는 것은 class
가 유일)
class
는 타입이자 값이 되지만 인스턴스의 타입이 되므로 class
자체의 타입이 필요하다면 typeof
를 사용해야 한다.
인터페이스에서 new
연산자를 붙이면 클래스 생성자를 타이핑 할 수 있다.
interface Person {
new (name: string): {
name: string;
};
}
enum
에 다른 숫자를 할당하면 다음 값은 저절로 이전 값에서 1을 더한 값으로 할당된다.enum Level {
NOVICE = 3,
INTERMEDIATE, //4
ADVANCED = 7
MASTER, //8
}
enum
에 숫자가 아닌 문자열을 할당하면 그 다음 값부터는 모두 직접 값을 할당해야 한다.infer E
에서 E
)는 컨디셔널 타입에서 참 부분에서만 사용할 수 있다.type El<T> = T extends (infer E)[] ? never : E //X, Cannot fine name 'E'.
type Union<T> = T extends { a: infer U, b: infer U } ? U : never;
type Result1 = Union<{ a: 1 | 2, b: 2 | 3 }> //type Result1 = 1 | 2 | 3
type Intersection<T> = T extends {
a: (pa: inter U) => void,
b: (pb: inter U) => void,
} ? U : never;
type Result2 = Intersection<{
a(pa: 1 | 2): void,
b(pb: 2 | 3): void,
}>; //type Result2 = 2
never
가 된다.타입 좁히기는 자바스크립트에서도 실행할 수 있는 코드여야 하기 때문에 자바스크립트 문법을 사용해야 한다.
재귀 타입은 타입 인수로 사용할 수 없다.
type T = number | string | Record<string, T> //X
type T = number | string | {[key: string]: T} //O
타입스크립트 4.9 버전에 추가된 satisfies
연산자를 사용하여 타입을 추가적으로 검사할 수 있다.
const universe = {
sun: 'start',
sriius: 'start', //sirius 오타로 에러 발생
earth: { type: 'planet', parent: 'sun' },
} satisfies {
[key in 'sun' | 'sirius' | 'earth']: { type: string, parent: string } | string
};
타입을 주장할때는 그 타입이 일시적이므로, 변수에 적용해야만 타입이 유지된다.
객체 타입이 아니더라도 &
연산자를 사용할 수 있다.(ex) 브랜딩 기법)
type Brand<T, B> = T & { _brand: B };
type KM = Brand<number, 'km'>;
type Mile = Brand<number, 'mile'>;
지금까지 2장 기본 문법 익히기를 정리하였는데, 알지 못했던 내용들이 많았다. 실무에서는 아직 복잡한 타입을 선언할 기회가 없어서 접할 기회가 없던 연산자나 타입스크립트가 동작하는 방식에 대해서 알 수 있었고, 추후에 패키지를 만들때 타입 정의에 활용할 수 있는 기능들을 배울 수 있었다. 책의 뒷부분에는 lib.es5.d.ts의 내부 코드나 실제 라이브러리나 환경에서 타입을 사용하는 방법들이 설명되어 있어서 이후 내용도 정리해 볼 예정이다.