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

유한별·2025년 2월 6일
0
post-thumbnail

[Medium] 15. Last of Array

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

문제

배열 T를 사용하고 마지막 요소를 반환하는 제네릭 Last<T>를 구현합니다.

예시

type arr1 = ["a", "b", "c"];
type arr2 = [3, 2, 1];

type tail1 = Last<arr1>; // expected to be 'c'
type tail2 = Last<arr2>; // expected to be 1

정답

type Last<T extends any[]> = T extends [...infer Rest, infer LastElement]
  ? LastElement
  : never;

설명

  • 898-includes 문제 참고
  • 스프레드 연산자를 이용해 배열을 분해하고, 마지막 요소를 반환

[Medium] 16. Pop

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

문제

배열 T를 사용해 마지막 요소를 제외한 배열을 반환하는 제네릭 Pop<T>를 구현합니다.

예시

type arr1 = ["a", "b", "c", "d"];
type arr2 = [3, 2, 1];

type re1 = Pop<arr1>; // expected to be ["a", "b", "c"];
type re2 = Pop<arr2>; // expected to be [3, 2];

정답

type Pop<T extends any[]> = T extends [...infer Rest, infer Last] ? Rest : [];

설명

  • 15번 문제와 동일하게 스프레드 연산자를 이용해 배열을 분해하고, 마지막 요소를 제외한 배열을 반환

[Medium] 20. Promise.all

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

문제

Type the function PromiseAll that accepts an array of PromiseLike objects, the returning value should be Promise<T> where T is the resolved result array.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, "foo");
});

// expected to be `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const);

정답

type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;

declare function PromiseAll<T extends any[]>(
  values: readonly [...T]
): Promise<{
  [K in keyof T]: Awaited<T[K]>;
}>;

에러 케이스

declare function PromiseAll<T extends any[]>(
  values: T
): Promise<{ [K in keyof T]: T[K] extends Promise<infer U> ? U : T[K] }>;

설명

  • PromiseAll 함수는 배열의 각 요소를 Promise로 감싸서 반환하는 함수
  • 우선 테스트케이스를 보면 readonly 배열(튜플)과 일반 배열이 모두 존재함
  • 따라서 최초 T는 any[]로 받고, 이를 튜플로(readonly)로 변환하기 위해 readonly [...T]를 사용
  • 이후 배열의 각 요소를 Promise로 감싸서 반환하기 위해 Promise<{ [K in keyof T]: Awaited<T[K]>; }>를 사용
  • { [K in keyof T]: Awaited<T[K]> }는 배열의 각 요소를 순회하며 키를 추출하고, 각 요소를 Awaited<T[K]>로 변환하여 반환
  • Awaited<T[K]>T[K]Promise라면 Promise의 내부 타입을 추출하고, 그렇지 않다면 T[K]를 반환

추가 질문

values에 readonly [...T]가 가능한 이유는?

  • 기존 T extends any[]에서 T는 배열이지만 튜플이 될 수도 있고, 일반 배열이 될 수도 있음
  • 따라서 readonly [...T]를 사용하여 튜플로 변환
declare function Example<T extends any[]>(values: readonly [...T]): T;
declare function Example2<T extends any[]>(values: T): T;

const test1 = Example([1, 2, 3]); // T = [number, number, number] (튜플)
const test2 = Example2([1, 2, 3]); // T = number[] (일반 배열)
  • TypeScript는 readonly [...T]에 맞춰 T를 튜플로 유추하려고 시도함
  • values가 일반 배열(number[])이면 TypeScript는 가능한 한 readonly 속성을 유지하려고 함
  • 결과적으로 T[number, number, number]처럼 튜플로 추론됨
  • readonly 옵션을 붙여주지 않으면 Example2처럼 일반 배열로 추론됨

왜 Awaited가 필요한가?

  • Awaited 대신 T[K] extends Promise<infer U> ? U : T[K]를 사용하면 오류 발생
  • Typescript에서 조건부 타입(extends)를 사용할 때, T가 유니온(A | B)이면 각각 개별적으로 평가한 후, 결과를 다시 유니온으로 합침
  • 이 때, T[K] 자체가 유니온 타입이기 때문에(이 문제에서는 number | Promise<number>), T[K]가 리턴될 경우 number | Promise<number>로 추론되어 오류 발생
  • 따라서 Awaited<T[K]>를 사용하여 T[K]가 리턴될 경우 number로 추론되도록 함

Reference

[Medium] 62. Type Lookup

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

문제

때때로 유니온 타입의 특정 속성을 기준으로 조회할 수도 있습니다.

이 챌린지에서는 유니온 타입 Cat | Dog에서 공통으로 사용하는 type 필드를 기준으로 해당하는 타입을 얻고자 합니다. 다시 말해서, 다음 예시에서는 LookUp<Cat | Dog, 'dog'>으로 Dog 타입을, LookUp<Cat | Dog, 'cat'>으로 Cat 타입을 얻을 수 있습니다.

interface Cat {
  type: "cat";
  breeds: "Abyssinian" | "Shorthair" | "Curl" | "Bengal";
}

interface Dog {
  type: "dog";
  breeds: "Hound" | "Brittany" | "Bulldog" | "Boxer";
  color: "brown" | "white" | "black";
}

type MyDogType = LookUp<Cat | Dog, "dog">; // 기대되는 결과는 `Dog`입니다.

정답

type LookUp<U, T> = U extends { type: T } ? U : never;

설명

  • 유니온 타입으로 주어지는 U를 분배법칙을 이용해 타입 조회
  • U{ type: T } 형태를 포함하는지 확인
  • 포함한다면 U를 반환
  • 포함하지 않는다면 never 반환

[Medium] 106. Trim Left

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

문제

정확한 문자열 타입이고 시작 부분의 공백이 제거된 새 문자열을 반환하는 TrimLeft<T>를 구현하십시오.

type trimed = TrimLeft<"  Hello World  ">; // expected to be 'Hello World  '

정답

type TrimLeft<S extends string> = S extends `${" " | "\n" | "\t"}${infer Rest}`
  ? TrimLeft<Rest>
  : S;

설명

  • 문자열 S" " | "\n" | "\t"로 시작하는지 확인
  • 시작하면 문자열 S에서 시작 부분의 공백을 제거한 새 문자열(Rest)을 반환
  • 시작하지 않으면 주어진 문자열 S를 그대로 반환

[Medium] 108. Trim

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

문제

정확한 문자열 타입이고 시작 부분의 공백이 제거된 새 문자열을 반환하는 Trim<T>를 구현하십시오.

type trimmed = Trim<"  Hello World  ">; // expected to be 'Hello World'

정답

type TrimLeft<S extends string> = S extends `${" " | "\n" | "\t"}${infer Rest}`
  ? TrimLeft<Rest>
  : S;

type TrimRight<S extends string> = S extends `${infer Rest}${" " | "\n" | "\t"}`
  ? TrimRight<Rest>
  : S;

type Trim<S extends string> = TrimRight<TrimLeft<S>>;

설명

  • 106번 TrimLeft<S> 활용해 문자열 S에서 시작 부분의 공백을 제거한 새 문자열을 반환
  • 같은 원리로 TrimRight<S>를 정의해 문자열 S에서 끝 부분의 공백을 제거한 새 문자열을 반환
  • Trim<S>: TrimLeft<S>TrimRight<S>를 조합하여 문자열 S에서 양쪽 끝의 공백을 제거한 새 문자열을 반환
profile
세상에 못할 일은 없어!

0개의 댓글