일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- prisma 파일 분리
- Type Challenge
- 스터디
- 이펙티브 타입스크립트
- 타입챌린지
- TypeScript
- equal 타입
- 코딩테스트
- nestjs
- 백엔드
- 타입스크립트
- configservice
- 월간cs
- type-safe configservice
- network
- typeorm
- 모던자스
- chainable options
- npm
- configmodule
- 회고
- 와글와글
- type challenge equal type
- 타입 챌린지
- 타입스크립트 타입챌린지
- TypeScript 타입챌린지
- node.js
- HTTP
- 해커톤
- typescript type challenge
- Today
- Total
iamkanguk.dev
[타입챌린지-MEDIUM] 8: Readonly 2 본문
문제
정답
// 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
- https://kscodebase.tistory.com/632?category=1330674
- https://joshua1988.github.io/ts/guide/operator.html#union-type%EC%9D%98-%EC%9E%A5%EC%A0%90
'Type Challenge' 카테고리의 다른 글
[타입챌린지-MEDIUM] 5153: IndexOf (0) | 2024.04.09 |
---|---|
[타입챌린지-MEDIUM] 9: Deep Readonly (0) | 2024.04.04 |
[타입챌린지-MEDIUM] 3: Omit (0) | 2024.03.24 |
[타입챌린지-EASY] 189: Awaited (0) | 2024.03.18 |
[타입챌린지 - EASY] 14 - First of Array (0) | 2024.03.10 |