dev-hamster

[type-challenges] medium 01-04 본문

타입스크립트/type-challenges

[type-challenges] medium 01-04

dev-hamster 2024. 10. 17. 21:56

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 & BAB의 모든 속성을 포함하는 새로운 타입이 된다.

유니온 타입 |

두 개 이상의 타입 중 하나를 선택할 수 있는 새로운 타입을 생성한다. A | BA또는 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