-
[Type-challenges] 난이도 Medium - (6)Front-End(Web)/Typescript 2023. 2. 27. 04:00반응형
Github 챌린지 문제들을 풀고 관련된 내용을 정리하면서, 부족했던 타입스크립트 기본지식을 다지고자 한다. (주 1-2회)
https://github.com/type-challenges/type-challenges
📘 목차 - Medium
- RequiredByKeys
- Mutable
- OmitByType
- ObjectEntries
- Shift
- Tuple to Nested Object
📘 문제 및 풀이
1. RequiredByKeys
Implement a generic RequiredByKeys<T, K> which takes two type argument T and K.
K specify the set of properties of T that should set to be required. When K is not provided, it should make all properties required just like the normal Required<T>.
For exampleinterface User { name?: string age?: number address?: string } type UserRequiredName = RequiredByKeys<User, 'name'> // { name: string; age?: number; address?: string }
import type { Equal, Expect } from '@type-challenges/utils' interface User { name?: string age?: number address?: string } interface UserRequiredName { name: string age?: number address?: string } interface UserRequiredNameAndAge { name: string age: number address?: string } type cases = [ Expect<Equal<RequiredByKeys<User, 'name'>, UserRequiredName>>, Expect<Equal<RequiredByKeys<User, 'name' | 'age'>, UserRequiredNameAndAge>>, Expect<Equal<RequiredByKeys<User>, Required<User>>>, // @ts-expect-error Expect<Equal<RequiredByKeys<User, 'name' | 'unknown'>, UserRequiredName>>, ]
🖌 풀이
type Merge<T> = { [P in keyof T]: T[P] } type RequiredByKeys<T, K extends keyof T = keyof T> = Merge<{ [P in keyof T as P extends K ? P : never]-?: T[P] } & { [P in keyof T as P extends K ? never : P]: T[P] }>
지난 풀이의 7. PartialByKeys 를 응용한 풀이이다. (링크)
단, Optional한 키값을 Required로 바꿔주기 위해 -? 문법을 사용했다.
* Mapping Modifiers
Mapped Type을 만들 때, 프로퍼티에 +와 - 연산자를 사용할 수 있다.
이는, ?(optional), readonly 등의 조건을 추가할지 말지 여부로, 아무것도 기입하지 않으면 +이므로 -는 이에 반대로 적용한다는 의미이다. (참고링크)
2. Mutable
Implement the generic Mutable<T> which makes all properties in T mutable (not readonly).
For exampleinterface Todo { readonly title: string readonly description: string readonly completed: boolean } type MutableTodo = Mutable<Todo> // { title: string; description: string; completed: boolean; }
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface Todo1 { title: string description: string completed: boolean meta: { author: string } } type List = [1, 2, 3] type cases = [ Expect<Equal<Mutable<Readonly<Todo1>>, Todo1>>, Expect<Equal<Mutable<Readonly<List>>, List>>, ] type errors = [ // @ts-expect-error Mutable<'string'>, // @ts-expect-error Mutable<0>, ]
🖌 풀이
type Mutable<T extends object> = { -readonly [P in keyof T]: T[P] }
위의 Mapping Modifiers 를 활용한 풀이이다. 단, 에러 케이스처럼 객체 외 값이 들어오는 경우를 예외처리해준다.
3. OmitByType
From T, pick a set of properties whose type are not assignable to U.
For Exampletype OmitBoolean = OmitByType<{ name: string count: number isReadonly: boolean isEnable: boolean }, boolean> // { name: string; count: number }
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface Model { name: string count: number isReadonly: boolean isEnable: boolean } type cases = [ Expect<Equal<OmitByType<Model, boolean>, { name: string; count: number }>>, Expect<Equal<OmitByType<Model, string>, { count: number; isReadonly: boolean; isEnable: boolean }>>, Expect<Equal<OmitByType<Model, number>, { name: string; isReadonly: boolean; isEnable: boolean }>>, ]
🖌 풀이
type OmitByType<T, U> = { [P in keyof T as T[P] extends U ? never : P]: T[P] }
마찬가지로, 지난 풀이의 4. PickByType을 응용하면 된다. (링크)
4. ObjectEntries
Implement the type version of Object.entries
For exampleinterface Model { name: string; age: number; locations: string[] | null; } type modelEntries = ObjectEntries<Model> // ['name', string] | ['age', number] | ['locations', string[] | null];
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' interface Model { name: string age: number locations: string[] | null } type ModelEntries = ['name', string] | ['age', number] | ['locations', string[] | null] type cases = [ Expect<Equal<ObjectEntries<Model>, ModelEntries>>, Expect<Equal<ObjectEntries<Partial<Model>>, ModelEntries>>, Expect<Equal<ObjectEntries<{ key?: undefined }>, ['key', undefined]>>, Expect<Equal<ObjectEntries<{ key: undefined }>, ['key', undefined]>>, ]
🖌 풀이
type ObjectEntries<T> = { [P in keyof T]-?: [P, T[P] extends infer R | undefined ? R : T[P]]; }[keyof T];
먼저 Mapping Modifiers로 옵셔널 키값들을 제외해줬다.
그리고 Partial의 케이스에서, value가 옵셔널하게 undefined인 경우도 정상적으로 반영되도록 infer R | undefined 인 경우엔 R 타입만 빼내서 반영해준 것이다.
5. Shift
Implement the type version of Array.shift
For exampletype Result = Shift<[3, 2, 1]> // [2, 1]
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ // @ts-expect-error Shift<unknown>, Expect<Equal<Shift<[]>, []>>, Expect<Equal<Shift<[1]>, []>>, Expect<Equal<Shift<[3, 2, 1]>, [2, 1]>>, Expect<Equal<Shift<['a', 'b', 'c', 'd']>, ['b', 'c', 'd']>>, ]
🖌 풀이
type Shift<T extends any[]> = T extends [infer F, ...infer R] ? R : [];
지난 풀이의 1. Pop 문제보다 좀 더 쉽다. 배열의 나머지(R) 부분을 반환해주면 된다. 에러 케이스를 고려해 T를 배열로 지정한다. (링크)
6. Tuple to Nested Object
Given a tuple type T that only contains string type, and a type U, build an object recursively.
type a = TupleToNestedObject<['a'], string> // {a: string} type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}} type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type
/* _____________ Test Cases _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<TupleToNestedObject<['a'], string>, { a: string }>>, Expect<Equal<TupleToNestedObject<['a', 'b'], number>, { a: { b: number } }>>, Expect<Equal<TupleToNestedObject<['a', 'b', 'c'], boolean>, { a: { b: { c: boolean } } }>>, Expect<Equal<TupleToNestedObject<[], boolean>, boolean>>, ]
🖌 풀이
type TupleToNestedObject<T, U> = T extends [infer F extends PropertyKey, ...infer R] ? { [P in F]: TupleToNestedObject<R, U> } : U
배열의 분리와, 첫 값인 F를 키값으로 해서 유틸리티를 재귀적으로 돌려 객체를 만들면 된다.
F를 PropertyKey로 지정한 이유는, 이를 처리하지 않으면 키값 타입(string, number, symbol)에 부합하지 않을 수 있다는 에러가 발생하기 때문이다.
* PropertyKey
Javascript 객체의 키 값으로 사용가능한 타입인 string, number, symbol의 유니온 타입이다. (참고링크)
반응형'Front-End(Web) > Typescript' 카테고리의 다른 글
[Type-challenges] 난이도 Medium - (8) (0) 2023.02.28 [Type-challenges] 난이도 Medium - (7) (0) 2023.02.27 [Type-challenges] 난이도 Medium - (5) (0) 2023.02.23 [Type-challenges] 난이도 Medium - (4) (0) 2023.02.16 [Type-challenges] 난이도 Medium - (3) (0) 2023.02.14