콘텐츠로 이동

타입 챌린지 Basic 풀이

type-challenges의 Basic 난이도 문제를 풀어봅니다. 유틸리티 타입을 직접 구현하면서 Mapped Type, Conditional Type, infer 등 TypeScript 타입 시스템의 핵심 도구를 익힐 수 있습니다.

주어진 타입에서 특정 프로퍼티만 선택합니다.

interface Todo {
title: string;
description: string;
completed: boolean;
}
type MyPick<T, K extends keyof T> = {
[key in K]: T[key];
};
type TodoPreview = MyPick<Todo, "title" | "completed">;
// { title: string; completed: boolean }

모든 프로퍼티를 readonly로 만듭니다.

interface Todo {
title: string;
description: string;
}
type MyReadonly<T extends object> = {
readonly [key in keyof T]: T[key];
};
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar",
};
todo.title = "Hello"; // Error: cannot reassign a readonly property

튜플의 각 요소를 키이자 값으로 하는 객체 타입을 만듭니다.

const tuple = ["tesla", "model 3", "model X", "model Y"] as const;
export type TupleToObject<T extends readonly any[]> = {
[key in T[number]]: key;
};
type result = TupleToObject<typeof tuple>;
// { tesla: "tesla"; "model 3": "model 3"; ... }

튜플 타입의 원형은 readonly any[]입니다. T[number]로 모든 요소의 유니온을 추출합니다.

배열의 첫 번째 요소 타입을 추출합니다.

type arr1 = ["a", "b", "c"];
type arr2 = [3, 2, 1];
type First<T extends any[]> = T[0];
type head1 = First<arr1>; // "a"
type head2 = First<arr2>; // 3

튜플의 길이를 타입으로 추출합니다.

type tesla = ["tesla", "model 3", "model X", "model Y"];
type Length<T extends any[]> = T["length"];
type teslaLength = Length<tesla>; // 4

유니온에서 특정 멤버를 제외합니다.

type MyExclude<T, K extends T> = T extends K ? never : T;
type Result = MyExclude<"a" | "b" | "c", "a">; // "b" | "c"

Promise를 풀어서 내부 타입을 추출합니다. 중첩된 Promise도 처리해야 합니다.

type ExampleType = Promise<string>;
type MyAwaited<T extends Promise<any>> = T extends Promise<infer U>
? U extends Promise<any>
? MyAwaited<U>
: U
: never;
type Result = MyAwaited<ExampleType>; // string

Promise<Promise<string>>처럼 중첩된 경우에는 재귀를 통해 한 번 더 풀어줘야 합니다.

불리언 타입에 따라 분기하는 조건부 타입입니다.

type If<T extends boolean, U, V> = T extends true ? U : V;
type A = If<true, "a", "b">; // "a"
type B = If<false, "a", "b">; // "b"

두 튜플을 이어 붙입니다.

type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U];
type Result = Concat<[1, 2, 3], [2]>; // [1, 2, 3, 2]

튜플에 특정 타입이 포함되어 있는지 확인합니다.

type Includes<T extends any[], U> = {
[Key in T[number]]: true;
}[U] extends true
? true
: false;
type isPillarMen = Includes<["Kars", "Esidisi", "Wamuu", "Santana"], 3>; // false

튜플의 끝에 요소를 추가합니다.

type Push<T extends unknown[], U> = [...T, U];
type Result = Push<[1, 2], "3">; // [1, 2, "3"]

튜플의 앞에 요소를 추가합니다.

type Unshift<T extends unknown[], U> = [U, ...T];
type Result = Unshift<[1, 2], 0>; // [0, 1, 2]

함수의 매개변수 타입을 튜플로 추출합니다. infer를 사용해 함수 시그니처에서 매개변수를 추론합니다.

type MyParameters<T extends (...rest: any[]) => any> = T extends (
...rest: infer R
) => any
? R
: never;
const foo = (arg1: string, arg2: number): void => {};
type FunctionParamsType = MyParameters<typeof foo>;
// [arg1: string, arg2: number]