이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다.
// 정답 코드
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends Record<string, any> ?
T[P] extends Function ?
T[P] :
DeepReadonly<T[P]> :
T[P]
}
type DeepReadonly<T> = {
readonly [P in keyof T]: keyof T[P] extends never ?
T[P] : DeepReadonly<T[P]>
}
// test case
type cases = [
Expect<Equal<DeepReadonly<X1>, Expected1>>,
Expect<Equal<DeepReadonly<X2>, Expected2>>,
]
type X1 = {
a: () => 22
b: string
c: {
d: boolean
e: {
g: {
h: {
i: true
j: 'string'
}
k: 'hello'
}
l: [
'hi',
{
m: ['hey']
},
]
}
}
}
type X2 = { a: string } | { b: number }
type Expected1 = {
readonly a: () => 22
readonly b: string
readonly c: {
readonly d: boolean
readonly e: {
readonly g: {
readonly h: {
readonly i: true
readonly j: 'string'
}
readonly k: 'hello'
}
readonly l: readonly [
'hi',
{
readonly m: readonly ['hey']
},
]
}
}
}
type Expected2 = { readonly a: string } | { readonly b: number }
맨 처음 나의 코드는 이러했다.
// 틀린 코드
type DeepReadonly<T> = {
readonly [P in keyof T as P extends Record<string, unknown> ? DeepReadonly<P> : P]: T[P]
}
멍청하게 key값에다가 Record를 상속받는지 체크하고 있었다... P는 key값이고 T[P]가 value값이라 T[P]를 체크했어야하는데...
따라서 위의 답은 당연히 오답이다.
다음 나의 코드를 봐보자.
// 틀린 코드
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends Record<string, unknown> ?
DeepReadonly<T[P]> :
T[P]
}
value값인 T[P]가 객체형태면, 재귀혀태로 한 번 더 타입 검사를하고 아니면 T[P]를 그대로 리턴한다.
이 답도 틀렸다. 왜냐하면 value값으로 함수가 들어갈 경우, 함수는 name: string을 속성으로 가지고 있어서 Record<string, unknown>이라는 조건을 만족하기 때문에 재귀를 타버리게 되어 에러가 나게 된다.
그래서 한 번 더 체크를 해주었다.
// 틀린 코드
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends Record<string, unknown> ?
T[P] extends Function ?
T[P] :
DeepReadonly<T[P]> :
T[P]
}
T[P]가 Record형태일 때, T[P]가 함수이면 재귀를 타지 않도록 삼항 연산자를 한 번 더 추가해주었다. 그런데 여전히 에러가 난다... 왜일까 찾아봤더니 바로 unknown이 문제였다. unknown은 컴파일 시 타입을 구체적으로 지정하지 않으면 에러를 뱉는데 아무래도 그래서 안되는 것 같았다.
그래서 unknown을 any로 바꿔주었더니 에러를 뱉지 않았다.
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends Record<string, any> ?
T[P] extends Function ?
T[P] :
DeepReadonly<T[P]> :
T[P]
}
readonly [P in keyof T] : T[P] extends Record<string, any> ? : T의 키값을 P라고 하고 value값은 T[P]를 타입으로 가지는데, value값 T[P] (value == T[P]라고 생각하자)가 Record(객체) 형태일 때,
T[P] extends Function ? T[P] : DeepReadonly<T[P]> : T[P] : T[P]가 함수이면, T[P](value)를 리턴하고, 함수가 아니면 재귀를 실행시키며, Record 형태가 아니라면 T[P]를 리턴한다.
그리고 두 번째 답에 대한 해설이다.
type DeepReadonly<T> = {
readonly [P in keyof T]: keyof T[P] extends never ?
T[P] : DeepReadonly<T[P]>
}
readonly [P in keyof T]: keyof T[P] extends never ? : T의 키값을 P라고 하자. 그리고 value값 T[P]의 키값이 없다면
T[P] : DeepReadonly<T[P]> : T[P]를 그대로 리턴하고 아니라면 value값인 T[P]로 재귀를 실행한다.
'Language > Typescript' 카테고리의 다른 글
타입챌린지 : 12-Chainable Options (medium) (0) | 2023.03.21 |
---|---|
타입챌린지 : 10-Tuple To Union (medium) (0) | 2023.03.18 |
타입챌린지 : 8-Readonly 2 (medium) (0) | 2023.03.12 |
타입챌린지 : 3-Omit (medium) (0) | 2023.03.11 |
타입챌린지 : 2-Get Return Type (medium) (0) | 2023.03.08 |