두 개의 타입 인수 T
와 K
를 사용하는 PartialByKeys<T, K>
를 구성하세요.
K
는 옵셔널하며 T
의 프로퍼티로 이루어진 유니언 타입을 지정할 수 있습니다. K
를 제공하지 않는다면 Partial<T>
와 같이 모든 프로퍼티를 옵셔널하게 만들어야 합니다.
예시
interface User {
name: string;
age: number;
address: string;
}
type UserPartialName = PartialByKeys<User, "name">; // { name?:string; age:number; address:string }
접근 방식
코드
type PartialByKeys<
T,
K extends keyof T = keyof T,
U = {
[Key in keyof T as Key extends K ? Key : never]?: T[Key];
} & Omit<T, K>,
> = { [UK in keyof U]: U[UK] };
코드 설명
T
의 key
값을 순회하기 위해 Key in keyof T
를 사용K
타입에 해당하는 키 값만 옵셔널하게 처리하기 위해 Key extends K
조건을 추가K
타입에 해당하는 키 값만 옵셔널하게 처리U
라고 하자K
타입에 해당하지 않는 키 값들은 Omit<T, K>
타입으로 만들어 두 타입을 인터섹션 처리U
을 순회하여 객체로 반환두 개의 타입 인자 T
와 K
를 받는 제네릭 타입 RequiredByKeys<T, K>
를 구현하세요.
K
는 필수(required)로 설정되어야 하는 T
의 속성들을 지정합니다. K
가 제공되지 않을 경우, 일반적인 Required<T>
처럼 모든 속성을 필수로 만들어야 합니다.
예시
interface User {
name?: string;
age?: number;
address?: string;
}
type UserRequiredName = RequiredByKeys<User, "name">; // { name: string; age?: number; address?: string }
접근 방식
코드
type RequiredByKeys<T, K extends keyof T = keyof T> = Omit<T, K> &
Required<Pick<T, K>> extends infer O
? { [Key in keyof O]: O[Key] }
: never;
코드 설명
Omit<T, K>
: T
에서 K
키 값들을 제외한 타입Required<Pick<T, K>>
: T
에서 K
키 값들만 선택하고, 그 값들을 필수로 만든 타입extends infer O
: 위 두 타입을 인터섹션 처리하여 하나의 타입으로 만듦? { [Key in keyof O]: O[Key] }
: 위 타입을 객체로 반환제네릭 Mutable<T>
를 구현하세요. 이는 T
의 모든 속성을 가변(mutable)하게 만듭니다 (readonly가 아니게).
예시:
interface Todo {
readonly title: string;
readonly description: string;
readonly completed: boolean;
}
type MutableTodo = Mutable<Todo>; // { title: string; description: string; completed: boolean; }
접근 방식
접근 방식
-readonly
라는 키워드가 있네코드
type Mutable<T, K extends keyof T = keyof T> = {
-readonly [P in keyof T as P extends K ? P : never]: T[P];
};
실패 이유
접근 방식
코드
type Mutable<T> = T extends readonly [...infer R]
? [...R]
: { -readonly [P in keyof T]: T[P] };
실패 이유
접근 방식
코드
type Mutable<T extends object> = T extends readonly [...infer R]
? [...R]
: { -readonly [P in keyof T]: T[P] };
코드 설명
T
가 readonly
리스트 타입이라면, 해당 원소들을 스프레드 연산자로 풀어서 반환T
가 readonly
객체 타입이라면, 해당 속성들을 순회하며 -readonly
키워드를 붙여서 반환T
에서 U
타입에 할당할 수 없는 속성들의 집합을 선택하세요.
예시
type OmitBoolean = OmitByType<
{
name: string;
count: number;
isReadonly: boolean;
isEnable: boolean;
},
boolean
>; // { name: string; count: number }
접근 방식
코드
type OmitByType<T, U> = { [K in keyof T as T[K] extends U ? never : K]: T[K] };
코드 설명
T[K] extends U ? never : K
를 통해 값 타입이 U
에 할당 가능한지 확인T[K]
타입이 U
타입에 할당할 수 없는 경우 never
처리(키-값 쌍 제거)Object.entries
함수의 타입 버전을 구현하세요.
예시
interface Model {
name: string;
age: number;
locations: string[] | null;
}
type modelEntries = ObjectEntries<Model>; // ['name', string] | ['age', number] | ['locations', string[] | null];
접근 방식
코드
type ObjectEntries<T> = {
[K in keyof T]: [K, T[K] extends undefined ? undefined : T[K]];
}[keyof T];
실패 이유
접근 방식
-?
키워드 참고type Required<T> = {
[K in keyof T]-?: T[K];
};
참고
코드
type ObjectEntries<T> = {
[K in keyof T]-?: [K, T[K] extends undefined ? undefined : T[K]];
}[keyof T];
실패 이유
접근 방식
any | undefined
타입에 대해서 처리 해버리자코드
type ObjectEntries<T> = {
[K in keyof T]-?: [
K,
T[K] extends undefined ? undefined : Exclude<T[K], undefined>,
];
}[keyof T];
실패 이유
any | undefined
도 처리해버림Equal<ObjectEntries<{ key: string | undefined }>, ["key", string | undefined]>
이 테스트 케이스 실패접근 방식
코드
type IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? true : false;
type ObjectEntries<T> = {
[K in keyof T]-?: IsOptional<T, K> extends true
? [K, T[K] extends undefined ? undefined : Exclude<T[K], undefined>]
: [K, T[K] extends undefined ? undefined : T[K]];
}[keyof T];
코드 설명
{} extends Pick<T, K>
의 동작 원리: Pick<T, K>
는 T의 키 중 K를 선택하여 반환하는 타입이다.type Example = {
required: number;
optional?: string;
};
type PickRequired = Pick<Example, "required">;
// 결과: { required: number }
type IsOptional1 = {} extends PickRequired ? true : false;
// 결과: false
type PickedOptional = Pick<Example, "optional">;
// 결과: { optional?: string }
type IsOptional2 = {} extends PickedOptional ? true : false;
// 결과: true
{} extends PickedOptional
조건은 빈 객체가 PickedOptional
의 서브타입인지 확인한다.
만약 required 키가 있는 경우, {}
는 PickedOptional
의 서브타입이 아니므로 조건이 거짓이 된다.
따라서, {} extends Pick<T, K>
는 K
가 옵셔널한 키인 경우에만 참이 된다.
T[K] extends undefined ? undefined : ...
이 부분은 값이 undefined인 경우에는 undefined를 반환하고, 그렇지 않은 경우에는 처리된 값을 반환한다.
제네릭 Shift<T>
를 구현하세요. 이는 T
의 첫 번째 요소를 제거한 배열을 반환합니다.
예시
type Result = Shift<[3, 2, 1]>; // [2, 1]
접근 방식
코드
type Shift<T extends any[]> = T extends []
? []
: T extends [infer _, ...infer Rest]
? [...Rest]
: [];
코드 설명
T extends []
처리로 빈 배열 처리infer
를 통해 맨 앞의 요소를 선택해 나머지 배열을 반환