iamkanguk.dev

[타입챌린지-MEDIUM] 8: Readonly 2 본문

Type Challenge

[타입챌린지-MEDIUM] 8: Readonly 2

iamkanguk 2024. 3. 27. 17:12

문제

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

 

type-challenges/questions/00008-medium-readonly-2/README.md at main · type-challenges/type-challenges

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

github.com

 

정답

// 1번째 방법과 2번째 방법의 차이는 내장 메서드인 Omit을 사용했냐 안했냐의 차이!
// 다양하게 구현할 수 있었던 문제였던 것 같음. 하지만 어려웠음
// Intersection을 이용해야 겠다는 생각을 못함.

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

/* ------------- 2번째 방법 ------------- */
type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [P in K]: T[P]
} & {
  [P in keyof Omit<T, K>]: T[P]
}

// 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
}

 

풀이해설 및 후기

일단, 대충 어떤 식으로 구현을 해야겠다라고 생각은 했는데 Intersection을 사용해야 겠다는 생각을 전혀 못했다. 이 타입 챌린지를 풀어보면서 타입스크립트에 대해 자세히 공부해보려고 책을 구매해야 겠다는 생각이 많이 들었다.

 

먼저 방법들을 설명하기 전에 공통적으로 알아야 할 부분을 먼저 언급하자면, MyReadonly2<T, K>에서 K가 null일 수도 있다. K가 null이면 T의 모든 Property들을 readonly 처리를 해야한다. 그래서 필자가 작성한 정답을 보면 K extends keyof T = keyof T라고 되어있다. 이 부분을 포함시켜야 한다. 해당 표현은 Default 값을 설정해줄 수 있는 부분인데 K가 Null일 경우에는 keyof T로 설정하라는 의미이다. 꼭 참고하자!

1번째 방법

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

 

직관적인 풀이법이다. 우리는 "K에서 설정한 Property들이면 readonly 속성을 추가해주고, 그렇지 않으면 그대로 넣자" 만 지키면 된다. 중요한 점은 Intersection을 사용해야 한다는 점....! 그리고 이전 포스팅에서 배웠던 as(그런데)를 사용하면 쉽게 해결할 수 있었다.

  • readonly [P in keyof T as P extends K ? P : never]: T[P]: "readonly 속성을 앞에 추가해줄거야. P가 T의 key여야 해. 그런데 P가 K에 속해있으면 추가해주고, 그렇지 않으면 추가 안할게" 라는 뜻이다. 
    • Test-case 2번을 예시로 들어보면 { readonly title: string, readonly description?: string }
  • 그리고 우리는 &을 통해 반대 케이스도 엮어준다. (Intersection 설명 링크 = 여기!)
  • [P in keyof T as P extends K ? never : P]: T[P]: 위에와 반대로 P가 K에 속해있으면 never를, 속해있지 않으면 반환한다.
    • Test-case 2번을 예시로 들어보면 { completed: boolean }
  • 위의 2개의 결과를 엮으면 { readonly title: string, readonly description?: string, completed: boolean } 이 반환된다.

2번째 방법

/* ------------- 2번째 방법 ------------- */
type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [P in K]: T[P]
} & {
  [P in keyof Omit<T, K>]: T[P]
}

 

사실 1번째 방법이랑 동일한 방법이다. 하지만 TypeScript에서 제공하는 Omit을 사용했다는 것에 대해 차이가 있다. Omit<T, K>는 T에서 K를 제외한 나머지를 통해 타입을 정의하기 때문에 K에 속하는 것들만 readonly를 추가해주고, Omit을 통해 K에 속해있지 않은 것들만 readonly를 추가해주지 않는 식으로 구현하면 된다.


- https://kyounghwan01.github.io/blog/TS/fundamentals/utility-types/#partial

 

typescript - 유틸리티 타입 (Partial, Omit, Pick)

typescript - 유틸리티 타입 (Partial, Omit, Pick), 타입스크립트, ts

kyounghwan01.github.io

 

- https://kscodebase.tistory.com/632?category=1330674

 

Readonly type 구현

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

kscodebase.tistory.com

 

- 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