[TS] Type-Challenges 스터디 2주차

유한별·2025년 1월 17일
0
post-thumbnail

[Easy] 189. Awaited

View on GitHub: https://tsch.js.org/189

문제

Promise와 같은 타입에 감싸인 타입이 있을 때, 안에 감싸인 타입이 무엇인지 어떻게 알 수 있을까요?

예시: Promise<ExampleType>이 있을 때, ExampleType을 어떻게 얻을 수 있을까요?

정답

type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T;

설명

  • TPromiseLike라면 infer를 사용하여 내부 타입 U를 추출하고, 재귀적으로 MyAwaited<U>를 호출
  • TPromiseLike가 아니라면 최종적으로 T를 반환

추가 질문

PromiseLike를 사용하는가?

  • PromiseLikePromise와 비슷한 타입을 나타내는 인터페이스
  • PromiseLikethen 메서드만 지원, catchfinally 메서드는 지원하지 않음
  • { then: (onfulfilled: (arg: number) => any) => any }를 처리하기 위해 Promise가 아닌 PromiseLike를 사용 (Promise가 성립하려면 catchfinally가 있어야 함)
interface Promise<T> {
  then<TResult1 = T, TResult2 = never>(
    onfulfilled?: (value: T) => TResult1 | PromiseLike<TResult1>,
    onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>
  ): Promise<TResult1 | TResult2>;

  catch<TResult = never>(
    onrejected?: (reason: any) => TResult | PromiseLike<TResult>
  ): Promise<T | TResult>;

  finally(onfinally?: (() => void) | null): Promise<T>;
}
interface PromiseLike<T> {
  then<TResult1 = T, TResult2 = never>(
    onfulfilled?: (value: T) => TResult1 | PromiseLike<TResult1>,
    onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>
  ): PromiseLike<TResult1 | TResult2>;
}

infer란 무엇인가?

  • infer 키워드는 조건부 타입에 사용되며, 조건식이 참으로 평가될 때 사용할 수 있는 타입을 추론하는 데 필요한 도구

  • infer 키워드는 컨디셔널 타입과 함께 사용되어야 함

  • 컨디셔널 타입에서 추론하고자 하는 타입에 infer 키워드를 사용하여 추론

  • infer를 활용해 배열의 요소 타입, Promise의 결과 타입, 함수의 반환 타입을 추출할 수 있다.

// 배열의 요소 타입 추출
type ElementType<T> = T extends (infer U)[] ? U : T;

type Test1 = ElementType<number[]>; // number
type Test2 = ElementType<string[]>; // string
type Test3 = ElementType<boolean>; // boolean (배열이 아니므로 그대로 반환)
// Promise의 결과 타입 추출
type AwaitedType<T> = T extends Promise<infer U> ? U : T;

type Test1 = AwaitedType<Promise<string>>; // string
type Test2 = AwaitedType<Promise<number>>; // number
type Test3 = AwaitedType<number>; // number (Promise가 아니므로 그대로 반환)
// 함수의 반환 타입 추출
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Test1 = ReturnType<() => string>; // string
type Test2 = ReturnType<(x: number) => void>; // void
type Test3 = ReturnType<number>; // never (함수가 아니므로)

Reference

[Easy] 268. If

View on GitHub: https://tsch.js.org/268

문제

조건 C, 참일 때 반환하는 타입 T, 거짓일 때 반환하는 타입 F를 받는 타입 If를 구현하세요. Ctrue 또는 false이고, TF는 아무 타입입니다.

정답

type If<C extends boolean, T, F> = C extends true ? T : F;

설명

  • C extends true ? T : FCtrue일 때 T를 반환하고, false일 때 F를 반환
  • C extends true에서 extends는 조건부 타입 정의에 사용됨. 삼항 연산자와 비슷한 역할.

추가 질문

조건부 타입에서 extends는 모든 타입을 받을 수 있을까?

  • 조건부 타입에서 extends는 모든 타입을 받을 수 있음
  • 유니온 타입일 경우 분배 법칙이 적용되어 각 요소에 대해 개별적으로 판단
type Example<T> = T extends string ? true : false;
type Result = Example<"a" | 1>; // true | false
  • 서브타입 관계가 성립하는 타입은 true를 반환 (Intersection Type 포함)
// Subtype Relation
type IsSubType = { a: string } extends { a: string; b?: number } ? true : false; // true

// Intersection Type
type Intersection<T> = T extends { a: string } & { b: number } ? true : false;
type Result = Intersection<{ a: string; b: number }>; // true
  • any 타입은 모든 타입의 서브타입이므로 true를 반환
type IsAny<T> = T extends any ? true : false;
type Result = IsAny<any>; // true
  • never 타입은 평가되지 않음(never를 반환)
type IsNever<T> = T extends never ? true : false;
type Result = IsNever<never>; // never
  • 튜플과 배열도 평가 가능
type IsArray<T> = T extends any[] ? true : false;
type Result1 = IsArray<number[]>; // true
type Result2 = IsArray<number>; // false

Reference

[Easy] 533. Concat

View on GitHub: https://tsch.js.org/533

문제

JavaScript의 Array.concat 함수를 타입 시스템에서 구현하세요. 타입은 두 인수를 받고, 인수를 왼쪽부터 concat한 새로운 배열을 반환해야 합니다.

정답

type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U];

설명

  • 튜플과 배열 모두 받기 위해 readonly any[]를 사용
  • spread 연산자를 사용하여 두 배열을 합침
  • 타입 레벨에서는 객체의 스프레드는 지원하지 않음

추가 질문

Reference

[Easy] 898. Includes

View on GitHub: https://tsch.js.org/898

문제

JavaScript의 Array.includes 함수를 타입 시스템에서 구현하세요. 타입은 두 인수를 받고, true 또는 false를 반환해야 합니다.

정답

type MyEqual<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
    ? true
    : false;

type Includes<T extends readonly any[], U> = T extends [
  infer First,
  ...infer Rest,
]
  ? MyEqual<First, U> extends true
    ? true
    : Includes<Rest, U>
  : false;

설명

  • MyEqual 타입은 두 타입이 같은지 확인하는 타입. 두 타입이 같으면 true, 다르면 false를 반환
  • <T>() => T extends X ? 1 : 2 타입은 타입 TX의 서브타입이면 1, 아니면 2를 반환하는 타입
  • T extends [infer First, ...infer Rest] 타입은 타입 T가 배열이면 First는 첫 번째 요소, Rest는 나머지 요소들을 반환
  • 재귀적으로 FirstU를 비교하여 한 번이라도 같으면 true, 다르면 false를 반환

추가 질문

<T>() 는 무엇인가?

  • TypeScript에서 익명 제네릭 함수 타입을 정의하는 방식
  • T는 함수 내부에서 사용할 수 있는 제네릭 타입 매개변수
  • 아무 변수명이나 가능하지만(<A>), 제네릭 타입 매개변수라는 것을 명시해야 함

MyEqual에 대한 이해

  • MyEqual은 두 타입이 같은지 확인하는 타입
  • (<T>() => T extends X ? 1 : 2)TX의 서브타입이면 1, 아니면 2를 반환하는 타입
  • (<T>() => T extends Y ? 1 : 2)TY의 서브타입이면 1, 아니면 2를 반환하는 타입
  • Typescript는 함수 비교를 시행할 때, 구조적 동등성을 사용하여 비교 (JavaScript에서는 함수 비교를 시행할 때, 참조적 동등성을 사용하여 비교)
  • 구조적 동등성은 함수의 매개변수와 반환 타입의 구조만 비교하고, 함수 이름이나 함수 본문은 비교하지 않음
  • 따라서 MyEqual은 두 함수의 매개변수와 반환 타입이 같으면 true, 다르면 false를 반환하는 타입
type A = <T>() => T extends string ? 1 : 2;
type B = <T>() => T extends string ? 1 : 2;

type AreEqual = A extends B ? true : false;
// A와 B의 함수 타입 구조가 동일하므로, A extends B는 true로 평가됨
type A = <T>() => T extends string ? 1 : 2;
type B = <T>() => T extends number ? 1 : 2;

type AreEqual = A extends B ? true : false;
// A와 B의 함수 타입 구조가 다르므로, A extends B는 false로 평가됨
type A = <T>() => T extends string ? 1 : 2;
type B = <T>() => T extends string ? 1 : 3;

type AreEqual = A extends B ? true : false;
// A와 B의 함수 타입 구조가 다르므로, A extends B는 false로 평가됨

Reference

[Easy] 3058. Push

View on GitHub: https://tsch.js.org/3058

문제

Array.push의 제네릭 버전을 구현하세요.

정답

type Push<T extends any[], U> = [...T, U];

설명

  • [...T, U]는 타입 T의 배열에 타입 U를 추가한 새로운 배열을 반환
  • ...는 스프레드 연산자로, 배열을 풀어서 각 요소를 별도의 인수로 전달
  • T extends any[]는 타입 T가 배열이어야 함을 제약
  • U는 추가할 요소의 타입

추가 질문

Reference

[Easy] 3060. Unshift

View on GitHub: https://tsch.js.org/3060

문제

Array.unshift의 제네릭 버전을 구현하세요.

정답

type Unshift<T extends any[], U> = [U, ...T];

설명

  • [U, ...T]는 타입 U를 첫 번째 요소로 하고, 타입 T의 배열을 나머지 요소로 하는 새로운 배열을 반환
  • ...는 스프레드 연산자로, 배열을 풀어서 각 요소를 별도의 인수로 전달
  • T extends any[]는 타입 T가 배열이어야 함을 제약
  • U는 추가할 요소의 타입

추가 질문

Reference

[Easy] 3312. Parameters

View on GitHub: https://tsch.js.org/3312

문제

내장 제네릭 Parameters<T>를 이를 사용하지 않고 구현하세요.

정답

type MyParameters<T extends (...args: any[]) => any> = T extends (
  ...args: infer U
) => any
  ? U
  : never;

설명

  • T(...args: any[]) => any 형태(함수 타입)인지 확인
  • 맞다면 args 부분(= 함수의 파라미터들)을 infer U라고 두어 '함수 매개변수들의 튜플 타입'을 추론
  • 추론한 타입 U를 반환
  • 함수가 아니라면 never 반환
  • ...args는 아무 이름이어도 되고, TypeScript에서 그냥 ‘추론할 위치’가 rest parameter임을 표시하기 위한 placeholder

추가 질문

Reference

profile
세상에 못할 일은 없어!

0개의 댓글