이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다.
내가 생각한 처음 답은 다음과 같았다.
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
readonly [P in keyof T as P extends K ? P : never]: T[P]
T의 키값을 P로 선언하고 해당 P가 K이면 P를 readonly 형식으로 반환한다. 아니라면 never를 반환한다.
두 번째 풀이
type MyOmit과 MyReadonly를 정의한다. 아래 두 포스팅 참고
MyOmit<T, K> & MyReadonly<T>
MyOmit타입을 이용해 T에서 K만 제외한 결과를 리턴한 타입과
MyReadonly를 이용해 T를 readonly로 만든 타입을 &를 이용해 합친다(?)
여기서 질문!
MyReadonly<T>를 하면, MyOmit<T,K> 와 겹치는 부분이 있을 텐데
ex) readonly name: string 과 name: string
이 겹칠 수 있는데 어떻게 동작할까?
&은 두 타입 모두를 만족하는 타입을 만들 때 사용하기 때문에 이런 경우 교집합의 형태로 더 좁은(자세한) 범위를 가지는 프로퍼티를 따라간다. 따라서 readonly name을 따라가기 때문에 정상적으로 동작한다.
'Language > Typescript' 카테고리의 다른 글
타입챌린지 : 10-Tuple To Union (medium) (0) | 2023.03.18 |
---|---|
타입챌린지 : 9-Deep Readonly (medium) (0) | 2023.03.16 |
타입챌린지 : 3-Omit (medium) (0) | 2023.03.11 |
타입챌린지 : 2-Get Return Type (medium) (0) | 2023.03.08 |
타입챌린지 : 3312-Parameters (easy) (0) | 2023.03.06 |