일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 타입스크립트
- socket
- HTTP
- Node.js
- 프로그래머스
- TCP
- HTTP 완벽 가이드
- 수학
- Algorithm
- 자바스크립트
- Nestjs
- 타입 챌린지
- 프로그래머스 레벨 2
- 가천대
- BFS
- typescript
- 문자열
- 그래프
- 쉬운 문제
- type challenge
- 소켓
- javascript
- 백준
- 알고리즘
- 크롤링
- Crawling
- ip
- 레벨 1
- dfs
- dp
- Today
- Total
kakasoo
Exclude은 왜 T extends U ? never : T 인가? 본문
// Question : Exclude type을 정의하라
type question<T, U> = any;
TypeScript Utility type인 Exclude의 정의를 보면 T extends U ? never : T 라는 매우 생소한 표현이 등장한다.
아니, Exclude는 두번째 타입 파라미터로 받은 U를 제외한 나머지 타입을 반환하는데 왜 never 혹은 T 라는 타입이 되는가?
아래의 순서를 따라가면 그 의미를 이해할 수 있다.
// Answer : 아래와 같은 논리를 따라가볼 수 있다.
// 'a' | 'b' | 'c' 라는 타입에서 'a'를 exclude 한다고 가정한다.
type answer1 = 'b' | 'c';
type answer2 = never | 'b' | 'c';
주석과 같이 'a' | 'b' | 'c' 타입으로부터 'a' 타입을 제외하면 'b' | 'c' 타입이 된다.
이게 우리가 구하고자 하는 답일 텐데, 여기서부터 역으로 생각해보도록 하자.
그렇다면 이 answer1이라는 타입은 answer2라는 타입처럼 never를 포함한 타입으로 정의할 수 있다.
never는 아무것도 없다는 것이기 때문에, 이 타입을 포함한 유니온 타입은 포함하지 않는 유니온 타입과 동일하기 때문이다.
// a면 나오지 않고 ( = never ), 그 외에는 나와야 한다는 말을 이렇게 풀어쓸 수 있다.
type answer3 = ('a' extends 'a' ? never : never) | ('b' extends 'a' ? never : 'b') | ('c' extends 'a' ? never : 'c');
answer2 유니온 타입의 각 타입들은 다시, 위와 같이 extends 'a' ? 를 포함한 구문으로 변경할 수 있다.
해당 타입이 만약 'a' 타입이라면 never를, 즉 제외하라는 의미가 되고, 그렇지 않다면 원래의 타입을 그대로 돌려주라는 의미가 된다.
여기서 말하는 원래의 타입이란 answer2의 각 타입들을 의미한다.
// 어차피 'a' extends 'a'는 무조건 true이기 때문에 never가 나온다, 고로 never: never의 두번째 never는 뭐가 나오든 상관없으므로 'a'로 변형 가능하다.
type answer4 = ('a' extends 'a' ? never : 'a') | ('b' extends 'a' ? never : 'b') | ('c' extends 'a' ? never : 'c');
// extends 'a'를 기준으로 다시 이렇게 합칠 수 있다.
type answer5 = 'a' | 'b' | 'c' extends 'a' ? never : 'a' | 'b' | 'c';
이 타입의 'a' extends 'a' ? never : never는 성립하나 성립하지 않나 never를 반환하는 구문이 된다.
하지만 우리는 'a' extends 'a'가 무조건 성립한다는 것을 알기 때문에 성립하지 않을 경우에 대한 값을 바꿔도 동일함을 알 수 있다.
고로 짝을 맞추기 위해서 answer4에서는 'a' extends 'a' ? never : 'a'라는 식으로 변경한다.
그러면 이제 extends 'a'를 기준으로 다시 answer5와 같이 묶을 수 있다.
// conditional한 부분을 제너릭으로 바꿀 수 있고,
type answer6<U> = 'a' | 'b' | 'c' extends U ? never : 'a' | 'b' | 'c';
// 다시 앞 뒤가 똑같은 두 타입에 대해 제너릭을 사용하면 이렇게 추상화 가능하다.
type answer7<T, U> = T extends U ? never : T;
이렇게 묶고 나면 이제 이걸 제너릭으로 변경하는 것은 너무나 간단한 일이다.
Exclude 타입은 이런 식으로 이해할 수 있고, 제너릭이 유니온 타입이 될 수 있단 것을 이해하면 나머지는 일사천리다.
이 타입을 이해하는 데 어려웠던 것은 결국 T보다 작은 T의 부분집합이 T라는 모순이었을 테지만, 유니온에서는 이는 모순이 아니다.
왜냐하면 유니온 타입에서의 extends 문은, 하나의 식이라기보다 유니온으로 엮인 타입의 개수만큼의 식이 있는 셈이기 때문이다.
Exclude를 정의했다면 이런 생각도 가능할 것이다.
유니온 타입에서 특정 유니온과 겹치는 게 가능했다면, 어? 객체에서 특정 프로퍼티만 제거하는 것도 가능하지 않나?
'프로그래밍 > TypeScript' 카테고리의 다른 글
타입 레벨에서 포함 관계를 검사하는 Includes 타입 구현하기 (0) | 2023.02.09 |
---|---|
배열의 첫 번째 값을 가리키는 타입 (0) | 2023.02.07 |
TypeScript type level에서의 in의 의미 (0) | 2023.01.25 |
조건에 따라 다른 response type이 추론되게 하기 (0) | 2023.01.24 |
NTuple 타입 정의하기 (0) | 2023.01.21 |