정의된 2개의 type UserPart
와 type UserPart2
를 교차 타입을 통해 하나의 type User
로 정의하려고 한다.
하지만 위의 경우 UserPart.id
와 UserPart2.id
의 타입이 달라 user.id
의 타입은 never
가 된다.
위 경우 보통interface
의 extend를 활용한다.
여기서 살펴봐야하는 점은 interface User
에서 이미 타입 에러가 나온다는 것이다.
즉, interface를 사용하게 되면 타입들을 extending 할 때 이미 에러를 알 수 있게 된다.
type
키워드로 타입을 교차하는 경우, id
의 타입이 다르다는 것을 보장하지 않지만, interface
는 체크해준다.
Pick
은 존재하지 않는 프로퍼티를 인자로 넣을 수 없고 Omit
은 존재하지 않는 프로퍼티를 인자로 넣어도 체커에 걸리지 않는다.
Omit
이 위와 같이 작동하는 이유는 내부 동작 원리 때문인데,
type Omit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
즉, 제거할 키가 있는 경우 as P extends K ? never : P
에서 never를 반환하여 제거하게 된다. 하지만, 존재하지 않는 키라면 애초에 keyof T
조건에 만족하지 않으므로 동일한 타입이 반환된다.
이제 Omit
은 또 하나의 특징이 있는데, 분배적이지 않다는 것이다.
union
타입에 대해 공통적인 프로퍼티를 omit
한다고 하자. 기대하는 결과값은 각 유니언된 타입에 대해서 id
프로퍼티만 제거된 타입이다.
하지만 결과는 공통 속성만 남기고 모두 제거된 타입이 된다.
이유는 omit이 유니언 타입에 대해 omit을 수행할 때, 리터러블하게 각 타입을 순회하는 게 아니라 타입들을 다 합친 뒤 두 번째 인자로 받아온 키에 대해 omit을 수행하기 때문이다.
위 문제를 해결하기 위해서 분배적인 Omit을 커스텀 해야한다.
예상했던 결과대로 각 유니언 타입에 대해 Omit
을 하게 된다.
참고: total typescript