2달 전
TypeScript 기본 알아가기
3달 전
TypeScript 기본 알아가기
1년 전
Next.js starter (.feat TypeScript)
TypeScript 기본 알아가기
타입스크립트가 리턴되는 값을 알아서 추론한다. 굳이 선언하도록 할 필요가 없다.
type Callback = (message: string) => void;
const printf = (message: string) => { console.log(message); }; function printFunc(message: string) { console.log(message); } type Print = (message: string) => void; const printToday = (callback: Print) => { callback(new Date().toDateString()); }; printToday(printf); printToday(printFunc);
함수 선언식과 함수 표현식 간의 동일한 인터페이스만 따진다.
함수 생성자(new Function()
) 를 사용하면 TypeScript에서 도와줄 수 있는 부분이 없다.
JavaScript의 this는 선언된 문맥과 호출된 방법에 따라 변한다. 그래서 TypeScript에서 this를 사용하고 싶다면, 첫 번째 인자로 넣자. TypeScipt의 this는 예약어로 JavaScript의 this와는 다르다.
this로 첫 번째 인자로 선언하면, fancyDate
함수는 인자가 0개인 함수이며, apply
, call
과 같은 this를 변경하여 호출하는 함수에서만 올바로 작동하게 된다.
eslint 에서 no-invalid-this 옵션을 켜서 this의 위험성을 막자. tsconfig에서 noImplicitThis 옵션을 켜면 this 타입을 명시적으로 설정하도록 강제할 수 있다.
function* createNumbers(): IterableIterator<number> { let n = 0; while (true) { yield n++; } }
tsconfig 에서 downlevelIteration
옵션을 켜면 ES2015 이전 버전에서 커스텀 반복자를 활성화할 수 있다. 이 옵션을 켜면 용량이 꽤 커진다고 한다.
대부분의 경우는 단축형 호출 시그니처로 충분할 것이다.
// 단축형 호출 시그니처 type Callback = (message: string) => void;
그러나 좀 더 복잡한 타입 관계가 있는 함수를 표현하게 될 때 전체 호출 시그니처를 활용해야 한다. 아래에는 message
만 인자로 받는 호출 시그니처와 line
, message
둘다 받는 호출 시그니처를 둘 다 정의할 수 있다.
// 전체 호출 시그니처 type Callback = { (message: string) => void; (message: string, line: number) => void; } type createElement = { (tag: 'a'): HTMLAnchorElement; (tag: 'canvas'): HTMLCanvasElement; (tag: 'table'): HTMLTableElement; (tag: string): HTMLElement; }
전체 호출 시그니처를 사용하면 함수에 속성을 쉽게 설정할 수 있다.
type Callback = { (message: string) => void; (message: string, line: number) => void; isfulfilled: boolean; }
filter
함수를 예로 구현을 해보자. 우선 제네릭을 사용하지 않는다면
type Filter = { (array: number[], f: (item: number) => boolean): number[]; (array: string[], f: (item: string) => boolean): string[]; (array: object[], f: (item: object) => boolean): object[]; };
잘 정의된 것 같지만, 객체 타입일 때 정상적으로 호출 될까? 그렇지 않을 것이다. object
는 객체 내 속성에 대한 어떠한 정보도 없기 때문이다.
제네릭은 타입을 지정하지 않지만 사용한 타입을 타입스크립트가 추론하도록 맡긴다.
// 전체 호출 시그니처, 전체에 제네릭을 설정 type Filter<T> = { (array: T[], f: (item: T) => boolean): T[]; };
해볼만한 활동 map 함수를 제네릭으로 타입 선언 한다면? Map<K, V>
// 전체 호출 시그니처, 전체에 제네릭을 설정 type Filter<T> = { (array: T[], f: (item: T) => boolean): T[]; };
// 전체 호출 시그니처, 전체에 제네릭을 설정 type Filter = { <T>(array: T[], f: (item: T) => boolean): T[]; };
// 단축 호출 시그니처 type Filter<T> = (array: T[], f: (item: T) => boolean) => T[];
// 단축 호출 type Filter = <T>(array: T[], f: (item: T) => boolean) => T[];
제네릭에서 타입을 명시할 때는 모든 제네릭을 명시하거나 아얘 명시하지 말아야 한다.
function map<T, U>(array: T[], f: (item: T) => U[] { } // OK map([1, 2, 3], n => n % 2 === 0) // OK map<string, boolean>([1, 2, 3], n => n % 2 === 0) // NOPE map<string>([1, 2, 3], n => n % 2 === 0)
// Promise의 리턴 타입 const promise = new Promise<number>((resolve) => resolve(45));
// setTimeout 리턴 타입 const timeoutId: ReturnType<typeof setTimeout> = setTimeout(() => {}, 100);
// React의 이벤트 타입 const handleClick = (e: MouseEventHandler<HTMLButtonElement>): void => { ... } <button onClick={handleClick}>버튼</div>
2진 트리를 구현한다고 생각해보자. 2진 트리는 아래의 규칙을 따른다.
type TreeNode = { value: string; }; type LeafNode = TreeNode & { isLeaf: true; }; type InnerNode = TreeNode & { children: [TreeNode] | [TreeNode, TreeNode]; }; function add10<T extends TreeNode>(node: T): T { return { ...node, value: (Number(node.value) + 10).toString(), }; }