본문 바로가기
Language/Typescript

타입챌린지 : 8-Readonly 2 (medium)

by hsloth 2023. 3. 12.

 

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다.

 

https://github.com/type-challenges/type-challenges/blob/main/questions/00008-medium-readonly-2/README.md

 

GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge

Collection of TypeScript type challenges with online judge - GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge

github.com

 

내가 생각한 처음 답은 다음과 같았다.

type MyReadonly2<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P]
} & {
  readonly [P in keyof T as P extends K ? P : never]: T[P]
}

하지만, 이것은 K에 제공되지 않을 때, 모든 프로퍼티가 Readonly가 되어야 한다는 조건을 만족시키지 못했다...

그래서 찾아본 결과... K 값에 default 값으로 T의 key값을 전달해주면 해결이 된다는 걸 알았다.

 

// 1
type MyReadonly2<T, K extends keyof T = keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P]
} & {
  readonly [P in keyof T as P extends K ? P : never]: T[P]
}


// 2
type MyOmit<T, K> = {
  [P in keyof T as P extends K ? never : P]: T[P]
}

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

type MyReadonly2<T, K extends keyof T = keyof T> = MyOmit<T, K> & MyReadonly<T>;

// test case
type cases = [
  Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
  Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
  Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,
  Expect<Alike<MyReadonly2<Todo2, 'description' >, Expected>>,
]

// @ts-expect-error
type error = MyReadonly2<Todo1, 'title' | 'invalid'>

interface Todo1 {
  title: string
  description?: string
  completed: boolean
}

interface Todo2 {
  readonly title: string
  description?: string
  completed: boolean
}

interface Expected {
  readonly title: string
  readonly description?: string
  completed: boolean
}

첫 번째 풀이

type MyReadonly2<T, K extends keyof T = keyof T>

MyReadonly2 라는 타입은 T와 K를 제네릭 인자로 받는데, K는 T의 키 값이고, 디폴트 값은 T의 키 값이다.

 

[P in keyof T as P extends K ? never : P]: T[P]

T의 키값을 P로 선언하고, 해당 P가 K이면 never을 리턴하고 아니라면 P를 리턴한다.

 

& : intersection type을 이용하여 두 타입을 만족하는 타입을 만든다. 아래 링크 참고

https://joshua1988.github.io/ts/guide/operator.html#union-type%EC%9D%98-%EC%9E%A5%EC%A0%90

 

연산자를 이용한 타입 정의 | 타입스크립트 핸드북

Union Type 유니온 타입(Union Type)이란 자바스크립트의 OR 연산자(||)와 같이 A이거나 B이다 라는 의미의 타입입니다. 아래 코드를 보겠습니다. 위 함수의 파라미터 text에는 문자열 타입이나 숫자 타입

joshua1988.github.io

readonly [P in keyof T as P extends K ? P : never]: T[P]

T의 키값을 P로 선언하고 해당 P가 K이면 P를 readonly 형식으로 반환한다. 아니라면 never를 반환한다.

 

두 번째 풀이

type MyOmit과 MyReadonly를 정의한다. 아래 두 포스팅 참고

https://suloth.tistory.com/42

 

타입챌린지 : 3-Omit (medium)

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다. https://github.com/type-challenges/type-challenges/blob/main/questions/00003-medium-omit/README

suloth.tistory.com

https://suloth.tistory.com/26

 

타입챌린지 : 7-Readonly (easy)

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다. https://github.com/type-challenges/type-challenges/blob/main/questions/00007-easy-readonly/READ

suloth.tistory.com

 

MyOmit<T, K> & MyReadonly<T>

MyOmit타입을 이용해 T에서 K만 제외한 결과를 리턴한 타입과

MyReadonly를 이용해 T를 readonly로 만든 타입을 &를 이용해 합친다(?)

 

여기서 질문!

MyReadonly<T>를 하면, MyOmit<T,K> 와 겹치는 부분이 있을 텐데

ex) readonly name: string 과 name: string

이 겹칠 수 있는데 어떻게 동작할까?

&은 두 타입 모두를 만족하는 타입을 만들 때 사용하기 때문에 이런 경우 교집합의 형태로 더 좁은(자세한) 범위를 가지는 프로퍼티를 따라간다. 따라서 readonly name을 따라가기 때문에 정상적으로 동작한다.