[TS] Type-Challenges 스터디 9주차
![[TS] Type-Challenges 스터디 9주차](/_next/image?url=https%3A%2F%2Fvelog.velcdn.com%2Fimages%2Fhayou%2Fpost%2F8abf125b-6626-4276-af1f-0fca0e9e57e2%2Fimage.jpg&w=3840&q=75)
[Medium] 2757. PartialByKeys
문제
두 개의 타입 인수 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 }
시도 1 (정답)
접근 방식
- 키 값을 순회하면서, 조건부 처리를 해보자
코드
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을 순회하여 객체로 반환
[Medium] 2759. Required By Keys
문제
두 개의 타입 인자 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 }
시도 1 (정답)
접근 방식
- 옵셔널한 키 값들을 필수로 만들기 위해 Required를 사용
- Omit & Pick을 활용해 한번에 처리
코드
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] }: 위 타입을 객체로 반환
[Medium] 2793. Mutable
문제
제네릭 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; }
시도 1
접근 방식
- 키 순회 및 리맵핑 과정에서 조건부 처리로 해볼 수 있지 않을까?
- 구현 불가능
시도 2
접근 방식
- 아예 키값들을 따로 떼오고, 그걸 순회하며 뭔가 처리해보자
-readonly라는 키워드가 있네
코드
type Mutable<T, K extends keyof T = keyof T> = {
-readonly [P in keyof T as P extends K ? P : never]: T[P];
};
실패 이유
- 리스트 타입에 대해서 처리 불가능
시도 3
접근 방식
- 리스트 먼저 처리 후 나머지 타입에 대해 처리
코드
type Mutable<T> = T extends readonly [...infer R]
? [...R]
: { -readonly [P in keyof T]: T[P] };
실패 이유
- 타입 에러 처리 불가능
시도 4 (정답)
접근 방식
- 리스트나 객체가 아닌 값에 대해 에러 처리를 해줘야하기 때문에, object 타입만 받도록 설정
코드
type Mutable<T extends object> = T extends readonly [...infer R]
? [...R]
: { -readonly [P in keyof T]: T[P] };
코드 설명
- 만약
T가readonly리스트 타입이라면, 해당 원소들을 스프레드 연산자로 풀어서 반환 - 만약
T가readonly객체 타입이라면, 해당 속성들을 순회하며-readonly키워드를 붙여서 반환
[Medium] 2852. Omit By Type
문제
T에서 U 타입에 할당할 수 없는 속성들의 집합을 선택하세요.
예시
type OmitBoolean = OmitByType<
{
name: string;
count: number;
isReadonly: boolean;
isEnable: boolean;
},
boolean
>; // { name: string; count: number }
시도 1 (정답)
접근 방식
- 키 값 순회하면서 키 리맵핑으로 조건부 처리
코드
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처리(키-값 쌍 제거)
[Medium] 2946. Object Entries
문제
Object.entries 함수의 타입 버전을 구현하세요.
예시
interface Model {
name: string;
age: number;
locations: string[] | null;
}
type modelEntries = ObjectEntries<Model>; // ['name', string] | ['age', number] | ['locations', string[] | null];
시도 1
접근 방식
- 키를 순회하여 키-값 쌍을 tuple로 만들어 해당 tuple들의 tuple을 만들자
- 그렇게 만들어진 tuple들을 union으로 변경하여 반환
코드
type ObjectEntries<T> = {
[K in keyof T]: [K, T[K] extends undefined ? undefined : T[K]];
}[keyof T];
실패 이유
- 옵셔널한 값들에 대해서 처리 불가능
시도 2
접근 방식
- 같은 방식으로, 옵셔널한 값들을 처리할 방법을 생각해보자.
-?키워드 참고
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];
실패 이유
- partial에 대해서 처리가 안됨
시도 3
접근 방식
any | undefined타입에 대해서 처리 해버리자
코드
type ObjectEntries<T> = {
[K in keyof T]-?: [
K,
T[K] extends undefined ? undefined : Exclude<T[K], undefined>,
];
}[keyof T];
실패 이유
- K가 옵셔널일 때만 처리해야되는데, 마지막 예시와 같이 필수 요소의
any | undefined도 처리해버림 Equal<ObjectEntries<{ key: string | undefined }>, ["key", string | undefined]>이 테스트 케이스 실패
시도 4 (정답)
접근 방식
- 키 값이 옵셔널일 때 한 번 더 분기 처리 해보자.
코드
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를 반환하고, 그렇지 않은 경우에는 처리된 값을 반환한다.
[Medium] 3062. Shift
문제
제네릭 Shift<T>를 구현하세요. 이는 T의 첫 번째 요소를 제거한 배열을 반환합니다.
예시
type Result = Shift<[3, 2, 1]>; // [2, 1]
시도 1 (정답)
접근 방식
- 빈 배열 처리 후, infer를 통해 맨 앞의 요소를 날릴 수 있지 않을까?
코드
type Shift<T extends any[]> = T extends []
? []
: T extends [infer _, ...infer Rest]
? [...Rest]
: [];
코드 설명
- 최초
T extends []처리로 빈 배열 처리 infer를 통해 맨 앞의 요소를 선택해 나머지 배열을 반환
comments
loading…