dev-hamster
[type-challenges] medium 01-04 본문
Get Return Type
내장 제네릭 ReturnType<T>을 이를 사용하지 않고 구현하자
리턴되는 타입을 infer U로 추론하고 U를 반환한다. 하지만 함수 인수를 받는 타입을 unknown[] 으로 하면 테스트 케이스가 일부만 통과해서 any로 변경해 모두 통과되도록 했다.
// 아쉬운 정답
type MyReturnType<T extends (...args: any) => unknown> =
T extends (...args:any) => infer U
? U
: never;
함수 인자 타입을 never[] 로 하면 모든 테스트 케이스가 통과된다.
// 정답
type MyReturnType<T extends (...args: never[]) => unknown> =
T extends (...args: never[]) => infer R
? R
: never
unknown[]이 안되는 이유
unknown 은 모든 타입의 상위 타입이므로 모든 타입은 unknown에 할당할 수 있다. 그렇기 때문에 unknown 은 가장 일반적인 타입이기 때문에 구체적인 타입과 호환되지 않는다.
type foo = unknown extends string ? true : false; // false
never[] 를 사용하는 이유
never는 존재하지 않는 공집합이기 때문에 any를 포함해 어떤 값도 가질 수 없는 가장 구체적인 타입이어서 다른 타입들과 호환될 수 있다.
type foo = never extends string ? true : false; // true
Omit
T에서 K 프로퍼티만 제거해 새로운 오브젝트 타입을 만드는 내장 제네릭 Omit<T, K>를 이를 사용하지 않고 구현하자.
T의 프로퍼티 키에서 K가 아니라면 키값을 추출하는 코드를 의도했다. 이 코드는 infer의 잘못된 사용으로 동작하지 않는다.
// 오답
type MyOmit<T, K extends keyof T> = K extends infer Key keyof T ?
never :
{ Key: T[Key] }
정답 코드는 이렇다.
// 정답
type MyOmit<T, K extends keyof T> = {[Key in keyof T as Key extends K ? never: Key]: T[Key]}
as 키워드
as 키용해 이용해 맵드 타입의 키를 다시 매핑할 수 있다. 예를들어 모든 프로퍼티 키를 Hamster라고 키 이름을 다시 매핑할 수 있다.
type MappedTypeWithNewProperties<Type> = {
[Properties in keyof Type as 'Hamster']: Type[Properties]
}
Readonly 2
T에서 K 프로퍼티만 읽기 전용으로 설정해 새로운 오브젝트 타입을 만드는 제네릭 MyReadonly2<T, K>를 구현하세요. K가 주어지지 않으면 단순히 Readonly<T>처럼 모든 프로퍼티를 읽기 전용으로 설정해야 합니다.
K 프로퍼티는 readonly 속성을 부여하면 된다. 구현은 정답을 참고했다.
type MyReadonly2<T, K extends keyof T = keyof T> = { readonly [k in K]: T[k] } & Omit<T, K>;
K가 주어지지 않는 경우를 위해 K extends keyof T = keyof T default 값을 설정한다.
교차 타입 &
두 개 이상의 타입을 결합해 모든 타입의 속성을 포함하는 새로운 타입을 생성할 수 있다. A & B는 A와 B의 모든 속성을 포함하는 새로운 타입이 된다.
유니온 타입 |
두 개 이상의 타입 중 하나를 선택할 수 있는 새로운 타입을 생성한다. A | B 는 A또는 B 중 하나의 타입을 가질 수 있다.
Deep Readonly
객체의 프로퍼티와 모든 하위 객체를 재귀적으로 읽기 전용으로 설정하는 제네릭 DeepReadonly<T>를 구현하세요.
이 챌린지에서는 타입 파라미터 T를 객체 타입으로 제한하고 있습니다. 객체뿐만 아니라 배열, 함수, 클래스 등 가능한 다양한 형태의 타입 파라미터를 사용하도록 도전해 보세요.
중첩된 객체 프로퍼티의 타입을 반환하는 타입은 만들었는데 다음은 어떻게 해야하지
// 오답
type DeepReadonly<T> = T extends object ? DeepReadonly<T[keyof T]> : T
정답을 참고해서 readonly 타입을 반환하도록 했는데, 중첩된 객체에서는 readonly가 안된다. (아래 코드가 왜 잘못됐는지 모르겠다🥲 )
// 오답
type DeepReadonly<T> = T extends Record<PropertyKey, unknown> ?
{ readonly [P in keyof T]: DeepReadonly<T[P]> }
:
T;
type x = DeepReadonly<{l: [
'hi',
{
m: ['hey']
},
]}>;
// 결과
// type x = {
// readonly l: ["hi", {
// m: ["hey"];
// }];
// }
// 기댓값
// type x = {
// readonly l: readonly ["hi", {
// readonly m: readonly ["hey"];
// }];
// }
정답 코드를 보면 T에 프로퍼티 키로 접근할 수 있는 값이 없다면 T[K]를 반환하고 그렇지 않다면 재귀적으로 DeepReadonly<T[K]> 를 호출한다.
// 정답
type DeepReadonly<T> = {
readonly [K in keyof T]: keyof T[K] extends never ? T[K] : DeepReadonly<T[K]>
}
TypeScript 에서 {} vs Object vs object
{}는 빈 객체가 아닌 any non-nullsih value이므로undefined,null을 제외한 모든 타입을 할당할 수 있다.Object도{}와 동일하다.object는 원시값을 제외한 모든 값이 할당 가능하다.
빈 객체{} 타입만을 허용하고 싶다면 never를 사용하자. 자바스크립트의 객체 타입(중괄호로 이루어진 객체)만을 허용하고 싶다면 Record를 사용하면 된다.

'타입스크립트 > type-challenges' 카테고리의 다른 글
| [type-challenges] medium 10-13 (0) | 2024.10.22 |
|---|---|
| [type-chllenges] medium 05-08 (2) | 2024.10.22 |
| [type-challengs] easy 10-14 (0) | 2024.10.12 |
| [type-challenges] easy 06-09 (1) | 2024.10.11 |
| [type-challenges] easy 01-05 (2) | 2024.10.11 |