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

유한별·2025년 3월 14일
0
post-thumbnail

[Medium] 459. Flatten

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

문제

주어진 배열을 플랫한 배열 타입으로 바꾸는 Flatten 타입을 구현하세요.

문제 설명

  • 배열 타입을 플랫하게 만들어 반환하는 문제
  • 배열 안의 배열까지 처리해야 됨
  • 배열이 아닌 경우 에러 처리

정답

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

설명

  • 제네릭 Textends any[]를 활용해, 배열이 아닌 경우 에러 처리
  • [infer First, ...infer Rest]로 배열의 각 요소 확인
  • First가 배열인 경우, [...Flatten<First>, ...Flatten<Rest>]로 재귀 호출
  • First가 배열이 아닌 경우, [First, ...Flatten<Rest>]로 재귀 호출

오답노트

1차 시도

type Flatten<T> = T extends [infer First, ...infer Rest]
  ? First extends any[]
    ? [...Flatten<First>, ...Flatten<Rest>]
    : [First, ...Flatten<Rest>]
  : [];
  • 에러 처리를 빼먹음
  • 제네릭 T에 extends any[]를 활용해, 배열이 아닌 경우 에러 처리

[Medium] 527. AppendToObject

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

문제

주어진 인터페이스에 새로운 필드를 추가한 object 타입을 구현하세요. 이 타입은 세 개의 인자를 받습니다.

문제 설명

  • 인터페이스에 새로운 필드를 추가하는 문제
  • 새로운 필드가 추가된 인터페이스는 온전한 타입이어야 함(유니온 타입 등 X)

정답

type AppendToObject<
  T extends object,
  U extends string,
  V,
  MergedObject = T & Record<U, V>,
> = { [P in keyof MergedObject]: MergedObject[P] };

설명

  • MergedObjectTRecord<U, V>를 유니온 타입으로 합친 타입
  • [P in keyof MergedObject]MergedObject의 모든 키를 순회, MergedObject[P]MergedObject의 각 키에 해당하는 값을 반환
  • 이렇게 만들어진 타입 객체를 다시 인터페이스로 반환

오답노트

1차 시도

type AppendToObject<T extends object, U, V> = { [P in keyof T]: T[P] } & {
  U: V;
};
type ex1 = AppendToObject<test1, "home", boolean>;
// { key: "cat"; value: "green"; } & { U: boolean; }
  • U가 주어진 타입이 아닌, string U가 key 값이 됨
  • 또한 이 경우 단순히 유니온 타입을 반환
  • 유니온 타입이 아닌 타입을 반환해야 함

2차 시도

type AppendToObject<T extends object, U extends string, V> = {
  [P in keyof T]: T[P];
} & Record<U, V>;
type ex1 = AppendToObject<test1, "home", boolean>;
// { key: "cat"; value: "green"; } & Record<"home", boolean>
  • Record를 활용해 U를 key로 하는 객체를 생성
  • 여전히 유니온 타입을 반환

[Medium] 529. Absolute

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

문제

number, string, 혹은 bigint을 받는 Absolute 타입을 만드세요.
출력은 양수 문자열이어야 합니다.

문제 설명

  • 절대값을 구해 string으로 반환하는 문제
  • 음수인 경우 양수로 변환
  • 양수인 경우 그대로 반환

정답

type StringFormat<T extends number | string | bigint> = `${T}`;

type Absolute<T extends number | string | bigint> =
  StringFormat<T> extends `${infer First}${infer Rest}`
    ? First extends "-"
      ? Rest
      : `${First}${Rest}`
    : never;

설명

  • StringFormat<T>T를 문자열로 변환하는 타입
  • StringFormat<T>으로 변환된 문자열을 파싱하여, 첫 번째 문자가 -인 경우 뒤에 있는 문자열을 반환
  • 첫 번째 문자가 -가 아닌 경우, 그대로 반환

추가 질문

Typescript에서 bigint는 어떻게 표현하는가?

let foo: bigint = BigInt(100); // the BigInt function
let bar: bigint = 100n; // a BigInt literal
  • BigInt 함수를 통해 생성된 bigintnumber의 서브타입이 아님
  • 마찬가지로 numberbigint의 서브타입이 아님
type IsBigInt<T> = T extends bigint ? true : false;

type Test1 = IsBigInt<bigint>; // true
type Test2 = IsBigInt<number>; // false

type IsNumber<T> = T extends number ? true : false;

type Test3 = IsNumber<number>; // true
type Test4 = IsNumber<bigint>; // false
  • 여기서 사용된 _는 숫자 구분자임(단순히 가독성을 높이기 위함이며, bigint와 상관 없음)

Reference

[Medium] 531. String to Union

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

문제

문자열 인수를 입력받는 String to Union 유형을 구현하세요.
출력은 입력 문자열의 Union type이어야 합니다.

문제 설명

  • 문자열 인수를 입력 받을 경우, 각 문자를 유니온 타입으로 반환하는 문제
  • 문자열이 비어있는 경우 never를 반환

정답

type StringToTuple<S extends String> = S extends `${infer First}${infer Last}`
  ? [First, ...StringToTuple<Last>]
  : [];

type StringToUnion<T extends string> = StringToTuple<T>[number];

설명

  • StringToTuple<S extends String>은 문자열을 튜플로 변환하는 타입(298-length-of-string 참고)
  • 튜플로 변환된 타입은 [number]를 통해 유니온 타입으로 변환(10-tuple-to-union 참고)

[Medium] 599. Merge

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

문제

두 개의 객체를 병합하는 제네릭 Merge<F, S>를 구현하세요.

문제 설명

  • 두 개의 객체를 병합하는 문제
  • 두 번째 객체의 키가 첫 번째 객체의 키를 덮어씀
  • 단순한 인터섹션 타입이 아닌, 두 객체의 키를 모두 포함하는 타입을 반환

정답

type Merge<F, S> = {
  [P in keyof F | keyof S]: P extends keyof S
    ? S[P]
    : P extends keyof F
      ? F[P]
      : never;
};

설명

  • [P in keyof F | keyof S]를 활용해 두 객체의 키를 모두 순회
  • 만약 Pkeyof S에 속한다면, S[P]를 먼저 반환(덮어쓰기)
  • 만약 Pkeyof S에 속하지 않고, keyof F에 속한다면, F[P]를 반환

오답노트

1차 시도

type Merge<F, S, MergedObject = F & S> = {
  [P in keyof MergedObject]: MergedObject[P];
};
type example = Merge<Foo, Bar>;
// { a: number; b: never; c: boolean; }
  • MergedObject라는 FS의 유니온 타입을 선언해주고, 이를 순환하는 방식
  • 이 경우 중복되는 키의 경우, value가 never로 처리됨

추가 질문

왜 중복되는 키의 경우, value가 never로 처리되는지?

  • 인터섹션 타입의 경우, 동일한 키가 충돌할 경우에는 별도의 규칙이 적용됨
  • 예를 들어, { a: number } & { a: string }의 경우, { a: number & string }로 처리됨
  • 이 경우, a의 타입은 number & string이 되며, 이는 유효하지 않은 타입이므로 never로 처리됨

[Medium] 612. KebabCase

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

문제

  • camelCasePascalCasekebab-case 문자열로 수정하세요.
  • FooBarBaz -> foo-bar-baz

문제 설명

  • 문자열을 kebab-case로 변환하는 문제
  • 문자열 내에 대문자가 있는 경우, 대문자를 소문자로 변환하고 대문자 앞에 -를 붙임
  • 만약 맨 앞 글자가 대문자라면, 소문자로 변환하고 앞에 -를 붙이지 않음
  • 알파벳이 아닌 경우, 그대로 반환

정답

type IsAlphabet<T extends string> =
  Lowercase<T> extends Uppercase<T> ? false : true;

type IsUpperCase<T extends string> =
  IsAlphabet<T> extends true ? (Uppercase<T> extends T ? true : false) : false;

type MakeKebabCase<S extends string> = S extends `${infer First}${infer Rest}`
  ? IsUpperCase<First> extends true
    ? `-${Lowercase<First>}${MakeKebabCase<Rest>}`
    : `${First}${MakeKebabCase<Rest>}`
  : "";

type KebabCase<S extends string> = S extends `${infer First}${infer Rest}`
  ? IsUpperCase<First> extends true
    ? `${Lowercase<First>}${MakeKebabCase<Rest>}`
    : `${First}${MakeKebabCase<Rest>}`
  : "";

설명

  • IsAlphabet 타입은 문자열이 알파벳인지 확인하는 타입(만약 T가 알파벳이라면 Lowercase<T>Uppercase<T>의 서브타입이 될 수 없음)
  • IsUpperCase 타입은 문자열이 알파벳일 경우, 대문자인지 확인하는 타입
  • MakeKebabCase 타입은 First가 대문자일 경우, Lowercase<First>를 통해 소문자로 변환한 뒤 -를 붙이고 Rest를 재귀적으로 호출
  • KebabCase 타입은 최초의 First가 대문자일 경우, Lowercase<First>를 통해 소문자로 변환한 뒤 -를 붙이지 않고, RestMakeKebabCase 타입으로 호출

오답노트

1차 시도

type IsUpperCase<T extends string> = T extends Uppercase<T> ? true : false;

type MakeKebabCase<S extends string> = S extends `${infer First}${infer Rest}`
  ? IsUpperCase<First> extends true
    ? `-${Lowercase<First>}${MakeKebabCase<Rest>}`
    : `${First}${MakeKebabCase<Rest>}`
  : "";

type KebabCase<S extends string> = S extends `${infer First}${infer Rest}`
  ? IsUpperCase<First> extends true
    ? `${Lowercase<First>}${MakeKebabCase<Rest>}`
    : `${First}${MakeKebabCase<Rest>}`
  : "";

type ex1 = KebabCase<"foo-bar">;
// "foo--bar"
type ex2 = KebabCase<"foo_bar">;
// "foo-_bar"
type ex3 = KebabCase<"Foo-Bar">;
// "foo---bar"
  • 알파벳이 아닌 다른 문자열들의 경우, IsUpperCase가 무조건 true를 반환하여 -를 붙임
profile
세상에 못할 일은 없어!

0개의 댓글