icon

메티의 블로그

5주차 타입스크립트 스터디

5주차 타입스크립트 스터디

Tags
TypeScript
날짜
Feb 5, 2025
상태
공개

119 Capitalize

문제: 문자열의 첫 글자만 대문자로 바꾸고 나머지는 그대로 놔두는 Capitalize<T>를 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ type MyCapitalize<S extends string> = S extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : S; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<MyCapitalize<'foobar'>, 'Foobar'>>, Expect<Equal<MyCapitalize<'FOOBAR'>, 'FOOBAR'>>, Expect<Equal<MyCapitalize<'foo bar'>, 'Foo bar'>>, Expect<Equal<MyCapitalize<''>, ''>>, Expect<Equal<MyCapitalize<'a'>, 'A'>>, Expect<Equal<MyCapitalize<'b'>, 'B'>>, Expect<Equal<MyCapitalize<'c'>, 'C'>>, Expect<Equal<MyCapitalize<'d'>, 'D'>>, Expect<Equal<MyCapitalize<'e'>, 'E'>>, Expect<Equal<MyCapitalize<'f'>, 'F'>>, Expect<Equal<MyCapitalize<'g'>, 'G'>>, Expect<Equal<MyCapitalize<'h'>, 'H'>>, Expect<Equal<MyCapitalize<'i'>, 'I'>>, Expect<Equal<MyCapitalize<'j'>, 'J'>>, Expect<Equal<MyCapitalize<'k'>, 'K'>>, Expect<Equal<MyCapitalize<'l'>, 'L'>>, Expect<Equal<MyCapitalize<'m'>, 'M'>>, Expect<Equal<MyCapitalize<'n'>, 'N'>>, Expect<Equal<MyCapitalize<'o'>, 'O'>>, Expect<Equal<MyCapitalize<'p'>, 'P'>>, Expect<Equal<MyCapitalize<'q'>, 'Q'>>, Expect<Equal<MyCapitalize<'r'>, 'R'>>, Expect<Equal<MyCapitalize<'s'>, 'S'>>, Expect<Equal<MyCapitalize<'t'>, 'T'>>, Expect<Equal<MyCapitalize<'u'>, 'U'>>, Expect<Equal<MyCapitalize<'v'>, 'V'>>, Expect<Equal<MyCapitalize<'w'>, 'W'>>, Expect<Equal<MyCapitalize<'x'>, 'X'>>, Expect<Equal<MyCapitalize<'y'>, 'Y'>>, Expect<Equal<MyCapitalize<'z'>, 'Z'>>, ]
  • 배운 점
    • Uppercase 는 빌트인 되어있는 타입스크립트 유틸리티타입 이다.
      • /** * Convert string literal type to uppercase */ type Uppercase<S extends string> = intrinsic; //본질적인
    • intrinsic 키워드를 보고, 타입스크립트 엔진 내 빌트인 로직을 적용할 것으로 보임

116 Replace

문제: 문자열 S에서 From를 찾아 한 번만 To로 교체하는 Replace<S, From, To>를 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ type Replace<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer I}${From}${infer Rest}` ? `${I}${To}${Rest}` : S; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Replace<'foobar', 'bar', 'foo'>, 'foofoo'>>, Expect<Equal<Replace<'foobarbar', 'bar', 'foo'>, 'foofoobar'>>, Expect<Equal<Replace<'foobarbar', '', 'foo'>, 'foobarbar'>>, Expect<Equal<Replace<'foobarbar', 'bar', ''>, 'foobar'>>, Expect<Equal<Replace<'foobarbar', 'bra', 'foo'>, 'foobarbar'>>, Expect<Equal<Replace<'', '', ''>, ''>>, ]
  • 배운 점
    • 템플릿 리터럴 타입에서 infer 키워드를 사용하면 패턴 매칭 ex)정규표현식 처럼 이루어 진다.
  • 풀이
    • From 을 넣고 찾은 후, 앞의 문자열을 I, 뒤의 문자열을 Rest 로 나누어 보낸다.
    • 이때는 가장 먼저 나온 From 문자열 값을 기준으로 한다.
 

119 Replace All

문제: 주어진 문자열 S에서 부분 문자열 From을 찾아 모두 To로 교체하는 제네릭 ReplaceAll<S, From, To>을 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ type ReplaceAll<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer I}${From}${infer Rest}` ? `${I}${To}${ReplaceAll<Rest, From, To>}` : S; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<ReplaceAll<'foobar', 'bar', 'foo'>, 'foofoo'>>, Expect<Equal<ReplaceAll<'foobar', 'bag', 'foo'>, 'foobar'>>, Expect<Equal<ReplaceAll<'foobarbar', 'bar', 'foo'>, 'foofoofoo'>>, Expect<Equal<ReplaceAll<'t y p e s', ' ', ''>, 'types'>>, Expect<Equal<ReplaceAll<'foobarbar', '', 'foo'>, 'foobarbar'>>, Expect<Equal<ReplaceAll<'barfoo', 'bar', 'foo'>, 'foofoo'>>, Expect<Equal<ReplaceAll<'foobarfoobar', 'ob', 'b'>, 'fobarfobar'>>, Expect<Equal<ReplaceAll<'foboorfoboar', 'bo', 'b'>, 'foborfobar'>>, Expect<Equal<ReplaceAll<'', '', ''>, ''>>, ]
  • 배운 점
    • 위의 문제와 같다.
  • 풀이
    • 위의 문제는 첫 From 을 패턴 매칭해 변경해 주었으므로, Rest 를 재귀를 통한 나머지 문자열을 같은 방식으로 해결하면 된다.
 

191 Append Argument

문제: 함수 타입 Fn과 어떤 타입 A가 주어질 때 Fn의 인수와 A마지막 인수로 받는 Fn과 동일한 함수 유형인 G를 생성하세요.
/* _____________ 여기에 코드 입력 _____________ */ // type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer Args) => infer Ret ? (x: A, ...args: Args) => Ret : never; type AppendArgument<Fn extends Function, A> = Fn extends (...args: infer Args) => infer Ret ? (...args: [...Args, A]) => Ret : never; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type Case1 = AppendArgument<(a: number, b: string) => number, boolean> type Result1 = (a: number, b: string, x: boolean) => number type Case2 = AppendArgument<() => void, undefined> type Result2 = (x: undefined) => void type cases = [ Expect<Equal<Case1, Result1>>, Expect<Equal<Case2, Result2>>, // @ts-expect-error AppendArgument<unknown, undefined>, ]
  • 배운 점
    • 타입을 […Args, A] 와 같이 합칠 수 있다.
    • extends function 할 때 (…args: any[]): => any 이런식으로 화살표 함수로 하는게 좋다.
 

296 Permutation

문제: 주어진 유니언 타입을 순열 배열로 바꾸는 Permutation 타입을 구현하세요.
/* _____________ 여기에 코드 입력 _____________ */ type Permutation<T, E = T> = [T] extends [never] ? [] : T extends E ? [T, ...Permutation<Exclude<E, T>>] : []; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<Permutation<'A'>, ['A']>>, Expect<Equal<Permutation<'A' | 'B' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>, Expect<Equal<Permutation<'B' | 'A' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>, Expect<Equal<Permutation<boolean>, [false, true] | [true, false]>>, Expect<Equal<Permutation<never>, []>>, ]
  • 배운 점
    • 제네릭을 그대로 조건부 타입을 통해 연산을 할 시(naked type parameter), 분배 조건부 타입으로 분배된다. 이때, T 에 never 가 들어오면, 분배될 집합 자체가 없어, 연산 자체가 동작하지 않아 never 가 된다.
      • type Test<T> = T extends never ? : 1 : 0; type Test1 = Test<never>; //never type Test2 = never extends never ? : 1 : 0; // 1
        그래서 never 를 조건부 타입 연산에 명확하게 필터링 하고 싶다면 다음과 같이 타입 재정의를 해주면 된다.
        type Test<T> = [T] extends [never] ? : 1 : 0;
    • 제네릭에서 재귀 시 새로운 변수를 만들고 싶다면, 기본값을 넣은 두번째 매개변수를 추가해도 좋다.
    • 타입스크립트에서 유니온 타입은 순서가 없다. ‘A’ | ‘B’‘B’ | ‘A’ 는 같다. 분배 조건부 타입 연산 시 내부적인 순서는 있다.
      • 문자열 리터럴 유니온 정렬은 알파벳 순서
      • 숫자 리터럴 유니온은 오름차순
      • boolean 은 false
      • never 는 아예 없는 취급
        • type T1 = string | never; type T2 = string; type Test = Equal<T1, T2>; // true
  • 풀이
    • T extends E ? [T, …Permutation<Exclude<E, T>>] : [] 는 T 가 제네릭 그대로 사용했으므로 분배 조건부 타입으로 분배 된다.
      • type Permutation<T, E = T> = [T] extends [never] ? [] : T extends E ? [T, ...Permutation<Exclude<E, T>>] : []; type Test = Permutation<'A' | 'B'>; // ('A' extends 'A' | 'B' ? ['A', ...Permutation<Exclude<'A' | 'B', 'A'>] : []) | ('B' extends 'A' | 'B' ? ['B', ...Permutation<Exclude<'A' | 'B', 'B'>] : []) // Permutation<'B'> = ['B'] 이므로 풀어내면 ['A' | 'B'] | ['B' | 'A']
         

298 Length of String

문제: String#length처럼 동작하는 문자열 리터럴의 길이를 구하세요.
// type LengthOfString<S extends string> = S['length'] type LengthOfString<S extends string, Count extends unknown[] = []> = S extends `${infer First}${infer Rest}` ? LengthOfString<Rest, [...Count, First]> : Count['length']; /* _____________ 테스트 케이스 _____________ */ import type { Equal, Expect } from '@type-challenges/utils' type cases = [ Expect<Equal<LengthOfString<'kumiko'>, 6>>, Expect<Equal<LengthOfString<'reina'>, 5>>, Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>, ]
  • 배운 점
    • string 타입의 S[’length’]number 이고, Array 타입의 S[’length’] 는 리터럴 타입(숫자)이다.
  • 풀이
    • string 타입을 받아, 그 길이에 맞는 배열을 만들고, 그 배열의 length 를 return
    • 패턴매칭 첫번째는 첫번째 문자 / 나머지 이므로 다음과 같은 재귀를 통해 Count 배열을 쌓아감