타입스크립트를 사용할 때 타입을 조금 더 편하게 사용할 수 있는 팁들을 준비해보았습니다.
Roll 타입으로 예쁘게 펼쳐보기
type Roll<T> = {
[K in keyof T]: T[K];
};
// & {};
Matt Pocock이 고안한 Roll이라는 타입은 네이버의 infer, never만 보면 두려워지는 당신을 위한 타입 추론에서 알게된 핵입니다. 다음과 같이 복잡한 타입을 사용하면 개발자는 해당 객체에 들어있는 타입이 정확히 무엇인지 확인하기 힘든데요. Roll을 이용하면 객체를 해부하여 볼 수 있게 됩니다.
type A = {
a: string;
b: number;
c: boolean;
}
type B = {
d: boolean;
e: number;
f: string[];
}
type C = A & B; // type C = A & B
type RolledC = Roll<A & B>
// type RolledC = {
// a: string;
// b: number;
// }
함수의 인자에 unkown과 never을 올바르게 사용하기
공변성과 반공변성을 가지는 지에 따라 unkown과 never을 올바르게 작성하여야 합니다. 함수의 반환값과 변수의 타입은 uknown
타이베 다른 모든 타입을 사용가능하지만 함수의 인자는 반공변성을 가지기 때문에 never
타입에 모든 타입을 사용가능합니다. 자세한 내용은 타입스크립트 심층 분석 4를 참고하세요.
type A = (a: any) => any;
type U = (u: unknown) => unknown;
type N = (n: never) => never;
const a: A = (a: string) => ""; // 인자와 반환값이 (never 제외)어떤 타입이라도 대입이 된다.
const u: U = (u: string) => ""; // Type 'unknown' is not assignable to type 'string'. 인자에서 문제가 발생한다.
const n: N = (n: string) => ""; // Type 'string' is not assignable to type 'never'. 반환값에서 문제가 발생한다.
타입가드 함수
function isNonNullable<T>(value: T): value is NonNullable<T> {
return value !== null && value !== undefined;
}
타입가드 함수는 특정 타입을 좁히는 역할을 하는 함수입니다. 타입스크립트에서는 특정 조건을 만족할 때 그 값을 명시적으로 추론할 수 있도록 돕는 함수들이 타입가드 함수입니다. 타입가드 함수는 주로 is 키워드를 사용하는 형태로 작성되어 타입스크립트에게 함수가 특정 타입으로 좁혀진다고 알릴 수 있습니다. 조건문에서 해당 함수가 true를 반환하면 해당 조건 내부에서 인자로 들어간 값은 is 키워드로 명시된 타입이 되고 그렇지 않다면 is 키워드로 명시된 타입이 아닌 다른 타입이라고 알릴 수 있습니다.
유니언 타입을 인터섹션 타입으로 전환하기
type UnionToIntersection<T> = (
T extends any
? (_: T) => void: never) extends (_: infer S) => void
? S
: never
T extends any ? ... : ...
에서, T
가 유니온 타입이라면 조건부 타입이 각 멤버에 대해 분산되어 각각의 멤버 타입에 대해 함수 타입을 생성합니다. 예를 들어, T
가 {a: number}|{b:string}
이라면 (_: {a: number}) => void | (_: {b: string}) => void;
가 됩니다. 이때 (_: T) => void
는 extends (_: infer S) => void
에 의해 평가되면서 infer S
는 {a: number}
가 되거나 {b: string}
이 됩니다.
그런데 infer
와 조건부 타입을 사용할 때, infer
가 반공변성을 가지는 자리(함수의 인자 자리)에서 사용되면 infer S
는 해당 값들이 모두 교차(인터섹션) 되는 타입을 추론하게 됩니다. 왜냐하면 해당 함수타입이 들어가는 자리에는 모든 인자를 가진 함수가 필요하기 때문입니다. 그래서 만약 infer
로 받게 되는 타입들이 결합될 수 없으면 추론을 실패하게 되어 never가 됩니다. 따라서 extends (_: infer S) => void
를 평가하는 과정에서 두 파라미터는 결합되어 최종적으로 인터섹션 타입으로 바뀌게 됩니다.
슬픈 이야기는 인터섹션 타입을 유니온 타입으로 만드는 건 불가능하다는 것입니다.
type A = {a: number};
type B = {b: string, c: number};
type G = UnionToIntersection<A | B>; // type G = A & B
함수 인자로 들어오는 내용에 따라 올바른 타입 매칭하기
function changeTypeBiaType<T extends Types["type"]>(
type: T,
value: Extract<Types, { type: T }>['value'],
): typeof value {
// 실제 로직 추가
return value;
}
위의 함수는 첫 번째 인자인 type
에 따라 올바른 타입을 매칭시켜주는 함수입니다. type
을 받은 뒤, 들어올 수 있는 타입 중 해당 type
과 같은 값을 가진 타입을 가지고와 value
와 매칭합니다. 다음과 같이 사용할 수 있습니다.
// 올바른 예시
const resultA = changeTypeBiaType("A", "Hello"); // string
const resultB = changeTypeBiaType("B", ["42", "world"]); // string[]
const resultC = changeTypeBiaType("C", 1); // number
const resultD = changeTypeBiaType("D", [1, 2, 3]); // number[]
// 잘못된 예시 - 오류 발생
const resultInvalidA = changeTypeBiaType("A", ["42", "world"]); // 오류: "A"는 string이어야 함
const resultInvalidB = changeTypeBiaType("B", "Hello"); // 오류: "B"는 string[]이어야 함
const resultInvalidC = changeTypeBiaType("W", "d") // 오류 type은 "A" | "B" | "C" | "D"이어야 함
객체 조작하기
readonly 속성 제거하기
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
-readonly
는 읽기 전용(readonly) 속성을 제거하는 데 사용됩니다.
type ReadonlyA = {
readonly a: number;
readonly b: string;
}
type A = Mutable<ReadonlyA>;
반대로 객체를 읽기 전용으로 만들려면 내장 제네릭인 Readonly<T>
를 사용할 수 있습니다.
객체의 모든 메서드 추출하기
type Method<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
객체의 모든 키를 순회하면서 값이 Funtion
타입을 가지는 경우를 모운 뒤 다시 키를 이용해 순회하면서 메서드의 타입을 가져올 수 있습니다.
type Person = {
name: string;
age: number;
greet: () => void;
sayGoodbye: (message: string) => void;
};
type FunctionKeys = Method<Person>;
중첩된 객체의 모든 값을 optional로 만들기
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
extends object
를 통해 특정 값이 객체면 재귀적으로 순회할 수 있습니다.
객체에서 특정 키들만 필수로 만들기
type RequiredPick<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
참고자료
NAVER ENGINEERING DAY 2024, infer, never만 보면 두려워지는 당신을 위한 타입 추론 - 응용 문제
Matt Pocock, Roll
typescript-challenges, issuecomment-1817784406
stackoverflow, Transform union type to intersection type
'개발 > TypeScript' 카테고리의 다른 글
타입스크립트 심층 분석 4 - 함수의 포함 관계는 뭔가 다르다. (공변성과 반공변성) (0) | 2024.09.20 |
---|---|
type-challenges 문제 풀이로 타입스크립트 타입 시스템 깊이 파기: Easy 난이도 - 3 (0) | 2024.08.23 |
타입스크립트 심층 분석 3 - 타입스크립트의 구조적 타이핑과 타입호환성 (0) | 2024.08.21 |
타입스크립트 심층 분석 2 - 타입 추론, 특수 타입, const와 let 키워드 차이(+참조값 및 원시값)에 따른 타입 추론 (0) | 2024.08.12 |
type-challenges 문제 풀이로 타입스크립트 타입 시스템 깊이 파기: Easy 난이도 - 2 (0) | 2024.08.05 |