728x90
조건부 타입
- 삼항연산자를 이용해 조건에 따라 타입을 결정한다.
type A = number extends string ? string : number; // type A = number
type ObjA = {
a: number;
};
type ObjB = {
a: number;
b: number;
};
type B = ObjB extends ObjA ? number : string; // true (ObjA = 슈퍼)
제네릭과 조건부 타입
function removeSpaces(text: string | undefined | null) {
if (typeof text === 'string') {
return text.replaceAll(' ', '');
} else {
return undefined;
}
}
let result = removeSpaces('hi im www');
// result.toUpperCase(); // result?. 로 쓰거나 위 코드에 as string으로 타입 단언 해줘야함
console.log(result);
- text의 타입을 string, undefined, null 중 하나라고 하면 함수 내부에서는 타입을 좁혔기 때문에 에러가 발생하지 않지만, 함수 바깥인 result에 string 관련 메서드를 적용하면 에러가 발생한다.
- 이 때 result가 반드시 있다고 .? 연산자를 써주거나, 매개변수를 전달하는 코드에 string 타입이라고 result의 타입을 단언해줘야만 한다.
- 또는 제네릭을 사용하는 방법도 있다.
function removeSpaces<T>(text: T): T extends string ? string : undefined {
if (typeof text === 'string') {
return text.replaceAll(' ', ''); // 에러
} else {
return undefined; // 에러
}
}
let result = removeSpaces('hi im www');
result.toUpperCase();
let result2 = removeSpaces(undefined); // undefined
- 매개변수 text의 타입을 타입 변수 T로 지정해서 해당 타입에 조건을 걸어준다.
- 하지만 함수 내부에서는 T가 뭔지 알 수 없다. (unknown으로 인지되기 때문)
- 따라서 any 타입으로 단언해줘야 한다.
function removeSpaces<T>(text: T): T extends string ? string : undefined {
if (typeof text === 'string') {
return text.replaceAll(' ', '') as any;
} else {
return undefined as any;
}
}
let result = removeSpaces('hi im www');
result.toUpperCase();
let result2 = removeSpaces(undefined); // undefined
- 하지만 return문에서 무조건 string, undefined로 받기로 한건데 무조건 any로 들어가게 돼서 검사가 안된다.
- 따라서 함수 오버로딩을 사용하면 위와 같은 문제도 해결된다.
function removeSpaces<T>(text: T): T extends string ? string : undefined;
function removeSpaces(text: any) {
if (typeof text === 'string') {
return text.replaceAll(' ', '');
} else {
return undefined;
}
}
- 오버로드 시그니처를 먼저 적어주면, 어처피 함수 구현부는 오버로드 시그니처를 따라가기 때문에 타입을 지정하지 않아도 된다.
- 매개 변수의 타입만 정의해주면 구현 시그니처 내부에서 조건부 타입을 추론할 수 있게 된다!
분산적인 조건부 타입
- 조건부 타입을 유니온과 같이 사용할 때 분산적으로 사용할 수 있도록 하는 문법
type StringNumberSwitch<T> = T extends number ? string : number;
let a: StringNumberSwitch<number>; // string
let b: StringNumberSwitch<string>; // number
let c: StringNumberSwitch<number | string>; // string | number
let d: StringNumberSwitch<boolean | number | string>; // string | number
- 변수 d의 경우 number | string | number로 중복되기 때문에 다시 string | number로 타입이 추론된다.
- 유니온으로 들어온 타입이 하나씩 조건부로 들어간다고 이해할 수 있다.
type Exclude<T, U> = T extends U ? never : T;
type A = Exclude<number | string | boolean, string>;
// never(공집합)는 사라짐
// number | never | boolean => number | boolean
type Extract<T, U> = T extends U ? T : never;
type B = Extract<number | string | boolean, string>;
// never(공집합)는 사라짐
// never | string | never => string
infer (Inference, 추론)
type FuncA = () => string;
type FuncB = () => number;
type ReturnType<T> = T extends () => string ? string : never;
type A = ReturnType<FuncA>; // string
type B = ReturnType<FuncB>; // never
- 반환값의 타입을 가져와 확인하는 ReturnType을 만들었는데, FuncB의 경우 제대로 number가 나오지 않는다. (당연함)
type FuncA = () => string;
type FuncB = () => number;
type ReturnType<T> = T extends () => infer R ? R : never;
type A = ReturnType<FuncA>; // string
type B = ReturnType<FuncB>; // number
- 이 때 string 타입 부분을 지우고 infer R ? R 로 바꾸면 의도했던대로 반환값의 타입이 추론된다.
- 원리는 () => infer R로, ()가 R의 서브 타입인지 확인하려면 들어갈 수 있는 타입을 추론하는 것 (같은 것끼리 서로 서브, 슈퍼로 간주)
type ReturnType<T> = T extends () => infer R ? R : never;
type C = ReturnType<number>; // never
- 그런데 변수 C의 경우 number가 아니라 never가 나온다.
- ReturnType<T> 타입은 주어진 함수 타입 T에서 함수의 반환값 타입을 추론하는 역할을 한다.
- 그러나 들어온 number는 함수 타입이 아니기 때문에 R을 추론할 수가 없고, 이 때는 false라고 이해해 never가 나오게 된다.
- 즉 T extends () => infer R 조건의 거짓이 되는 것이다.
// type PromiseUnpack<T> = any;
type PromiseUnpack<T> = T extends Promise<infer R> ? R : never;
// T는 Promise 타입이어야 한다.
// Promise 타입의 결과값 타입을 반환해야 한다.
type PromiseA = PromiseUnpack<Promise<number>>;
// number;
type PromiseB = PromiseUnpack<Promise<string>>;
// string;
- Promise 타입의 결과값 타입을 반환하려고 한다.
- A는 number, B는 string이 잘 나오면 된다.
- 먼저 PromiseUnpack 타입을 만들고 타입 변수 T를 넣은 후 any라고 한다.
- 이 때 매개변수로 들어오는 T는 Promise여야 한다.
- 따라서 any를 지우고, 들어온 T(Promise)는 Promise<infer R> 일때 R이라고 적어준다.
- 이 Promise 타입의 결과값을 infer하는데 true가 되기 위해 number, string으로 추론된다.
**출처: 한 입 크기로 잘라먹는 타입스크립트 (인프런, 이정환 강사님)
728x90
'❔ TypeScript > 💭 한 입 크기 TypeScript' 카테고리의 다른 글
[TypeScript] 타입스크립트 리액트 시작하기 (0) | 2024.01.02 |
---|---|
[TypeScript] 유틸리티 타입 (0) | 2023.12.30 |
[TypeScript] 타입 조작 (0) | 2023.12.22 |
[TypeScript] 프로미스와 제네릭 (0) | 2023.12.22 |
[TypeScript] 제네릭 클래스 (0) | 2023.12.22 |