Call Signatures
type Add = {
(a: number, b: number): number;
}
// type Add = (a: number, b: number) => number;
const add: Add = (a, b) => a + b
Call(=Function) Signature란 함수의 매개변수와 반환 값의 타입을 모두 type으로 미리 선언하는 것이다
* React에서 함수로 props를 보낼 때, 어떻게 작동할지 미리 설계 가능!
Overloading
Function(=Method) Overloading은 직접 작성하기보다 외부 패키지/라이브러리에 자주 보이는 형태로, 하나의 함수가 복수의 Call Signature를 가질 때 발생한다
type Add = {
(a: number, b: number): number,
(a: number, b: string): number
}
const add: Add = (a, b) => {
if (typeof b === "string") return a;
return a + b;
}
매개변수의 데이터 타입이 다른 경우 예외 처리
type Add2 = {
(a: number, b: number): number,
(a: number, b: number, c: number): number
}
const add2: Add2 = (a, b, c?: number) => {
if (c) return a + b + c;
return a + b;
}
매개변수의 수가 다른 경우 예외 처리
위와 같은 함수는 거의 없지만 패키지/라이브러리에서 활용될 수 있다
router.push("/home");
router.push({
path: "/home",
state: 1
});
예를 들어, Next.js의 라우터 push가 대충 두 가지 방법으로 페이지를 이동한다고 할 때,
type Config = {
path: string,
state: number
}
type Push = {
(config: Config): void,
(config: string): void
}
const push: Push = (config) => {
if (typeof config === "string") console.log(config);
else console.log(config.path);
}
패키지나 라이브러리는 위와 같이 두 가지 경우의 Overloading으로 디자인되어 있을 것이다
Generics & Polymorphism
type SuperPrint = {
(arr: number[]): void,
(arr: string[]): void,
(arr: boolean[]): void,
(arr: (number|string|boolean)[]): void
}
const superPrint: SuperPrint = (arr) => {
arr.forEach(e => console.log(e));
}
superPrint([1, 2, 3]);
superPrint(["a", "b", "c"]);
superPrint([true, false, true]);
superPrint([1, "b", true]);
위와 같이 다양한 경우를 커버하는 함수를 작성할 때, 모든 조합의 Call Signature를 concrete type으로 적어주는 일은 번거롭다
type SuperPrint = {
<T>(arr: T[]): T
};
// type SuperPrint = <T>(arr: T) => T;
const superPrint: SuperPrint = (arr) => arr[0]
// <number>(arr: number[]) => number
superPrint([1, 2, 3]);
// <string>(arr: string[]) => string
superPrint(["a", "b", "c"]);
// <boolean>(arr: boolean[]) => boolean
superPrint([true, false, true]);
// <string | number | boolean>(arr: (string | number | boolean)[]) => string | number | boolean
superPrint([1, "b", true]);
이 때, type에 Generic을 할당하면 호출된 값으로 concrete type을 가지는 Call Signature를 역으로 보여주는 다형성(Polymorphism)을 가진다
function superPrint<T>(a: T[]): T{
return a[0];
}
물론 함수에 바로 작성해줘도 무방하다
그렇다면 그냥 any를 넣는 것과 Generic의 차이는 무엇일까?
type SuperPrint = {
(arr: any[]): any
};
const superPrint: SuperPrint = (arr) => arr[0]
let a = superPrint([1, "b", true]);
// pass
a.toUpperCase();
any를 사용하면 위와 같은 경우에도 에러가 발생하지 않는다
type SuperPrint = {
<T>(arr: T[]): T
};
const superPrint: SuperPrint = (arr) => arr[0]
let a = superPrint([1, "b", true]);
// error
a.toUpperCase();
Generic의 경우 에러가 발생해 보호받을 수 있다
* Call Signature를 concrete type으로 하나씩 추가하는 구조
type SuperPrint = {
<T, M>(arr: T[], x: M): T
};
const superPrint: SuperPrint = (arr, x) => arr[0]
let a = superPrint([1, "b", true], "hi");
위와 같이 복수의 Generic을 선언해 사용할 수도 있다
type Player<T> = {
name: string,
extraInfo: T
};
type MePlayer = Player<MeExtra>;
type MeExtra = {age: number};
const player: MePlayer = {
name: "joseph",
extraInfo: {
age: 23
}
};
const player2: Player<null> = {
name: "Yee",
extraInfo: null
};
Generic은 위와 같이 원하는 만큼 커스텀 및 재사용이 가능하다
아마 직접 작성하기보다 패키지/라이브러리의 Generic을 활용하는 경우가 더 많을 것이다
const numArr: Array<number> = [1, 2, 3, 4];
const [state, setState] = useState<number>();
함수 뿐만 아니라 다양한 경우의 Generic을 활용할 수 있는데, 예를 들어 Array 기본 형태나 React의 useState가 Generic으로 디자인되어 있다
본 내용은 노마드코더의 'Typescript로 블록체인 만들기'를 바탕으로 작성되었습니다.
'Languages > TypeScript' 카테고리의 다른 글
[노마드코더] #5 Typescript Blockchain (0) | 2022.05.27 |
---|---|
[노마드코더] #4 Classes and Interfaces (0) | 2022.05.21 |
[노마드코더] #2 Overview of TypeScript (0) | 2022.05.14 |
[노마드코더] #1 Introduction (0) | 2022.05.03 |
[TIL] TypeScript 기초 (8) | 2022.01.18 |