유용한 타입 만들기

판단하는 타입만들기

타입스크립트를 작성할 때는 특정 타입이 무슨 타입인지 판단할 수 있어야 한다.

  • 그래야 타입을 컨디셔널 타입으로 제거할 수 도 있고, 그 타입만 추릴 수 있다.

IsNever

  • T에 never를 넣을 때 분배 법칙이 일어난것을 막기 위해 배열로 감싸는것을 확인

    • never를 유니온으로 생각, 제너릭과 유니온이 같이 쓰이면 분배 법칙이 발생

    • never에 분배 법칙이 발생하면 never가 된다.

type IsNever<T> = [T] extends [never] ? true : false

IsAny

  • string과 number는 겹치지 않아 extends 할수가 없다.

  • 또한 number & T는 number의 부분집합이므로 더욱 string과 겹치지 않음

  • But, T가 any라면 이야기 달라짐

  • any라면 number & T(any) 는 any가 되고 string 은 any를 extends 할 수 있게됨

type IsAny<T> = string extends (number & T) ? true : false

IsArray

  • 복잡한 이유

  • isArray<never>가 never가 되는것을 막기 위해 IsNever<T> extends true 필요

  • isArray<any>가 boolean이 되는것을 막기 위해 IsAny<T> extends true가 필요

  • isArray<readonly []>가 false가 되는것을 막기 위해 T extends readonly unknown[]이 필요

type IsArray<T> = IsNever<T> extends true
  ? false
  : T extends readonly unknown[]
    ? IsAny<T> extends true
    ? false
    : true
  : false;

IsTuple

  • 배열과 튜플의 차이는 튜플의 길이가 고정되어 있다는 것

  • 튜플이 아닌 배열은 length가 number

  • 튜플은 1,2,3 같은 고정된 개별 숫자가 됨

    • 그래서 number extends T["length"]가 false여야 되는것이 핵심

    • any 판별 조건도 여기서 같이 걸러짐

type IsTuple<T> = IsNever<T> extends true
  ? false 
  : T extends readonly unknown[]
    ? number extends T["length"]
      ? false
      : true
    : false;

IsUnion

  • U = T, T extends T는 무슨 의미인가?

  • T extends T는 항상 true 이지만 분배법칙을 만들기 위해 사용함

    • 유니온의 경우 컨디셔널 타입 제너릭과 만나면 분배법칙이 발생

    • T가 string | number 인경우 T extends T 는 string | number extends string | number 가 아니라 (string extends string | number) | (number extends string | number)가 된다.

    • [U] extends [T] 는 분배법칙이 일어나지 않게 해서 [string | number] extends [string] 또는 [string | number] extends [number]가 된다.

    • 최종적으로 false가 되어 union 타입인 IsUnion<string | number>는 true가 된다.

type IsUnion<T, U = T> = IsNever<T> extends true
  ? false
  : T extends T
    ? [U] extends [T]
    ? false
    : true
  : false;

집합 관련 타입 만들기

타입스크립트의 타입은 집합으로 생각해도 될 정도로 집합의 원리를 충실히 따름

  • 전체 집합은 unknown, 공집합은 never

  • 합집합 |, 교집합 &

차집합

A가 { name: string, age: number}, B는 { name: string, married: boolean } 인 경우 둘을 차집합 (A-B)하면 { age: number }가 나와야하고 (B-A)라면 { married: boolean } 이 나와야 한다.

type Diff<A, B> = Omit<A & B, keyof B>;
type R1 = Diff< {name: string, age: number}, {name: string, married: boolean}>;
// { age: number }

Omit

특정 객체에서 지정한 속성을 제거하는 타입

Diff 응용

Diff 타입을 조금 더 응용하면 대칭차집합도 찾아 낼 수 있다. (서로 겹치지 않는 타입 모두 합쳐놓은 것) 즉, 합집합에서 교집합을 뺀 것이라고도 볼 수 있음

type SymDiff<A,B> = Omit<A & B, keyof (A | B)>;
type R2 = SymDiff< {name: string, age: number}, {name: string, married: boolean}>;
// { age: number, married: boolean }

Exclude

어떤 타입 (A | B) 에서 다른 타입 (A & B)를 제거하는 타입

타입스크립트에서 부분집합이란?

A가 B 타입에 대입 가능하면 A는 B의 부분집합이다.

type IsSubset<A, B> = A extends B ? true : false;
type R1 = IsSubset<string, string | number>; // true
type R2 = IsSubset<{ name: string, age: number }, { name: string }>; // true
type R3 = IsSubset<symbol, unknown>; // true

Equal

두 타입이 동일하다는 판단

타입도 집합이므로 A가 B의 부분집합이고 B도 A의 부분집합이면 A와 B는 서로 동일하다는 듯

type Equal<A, B> = A extends B ? B extends A ? true : false : false;
  • 위 코드는 허점이 있음, true로 예상했으나 그렇지 않은 사레들이 존재

type R1 = Equala<boolean, true | false> // boolean
type R2 = Equal<never, never> // never
  • 유니온이 일어나는 분배법칙 때문 (never도 유니온이라 생각)

    • 분배법칙이 일어나지 않도록 배열로 감싸기

type Equal<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false
  • 단 위 Equal 타입은 any를 구분할 수 없다.

type Equal2<X, Y>
  = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
    ? true
    : false
  • 위 Equal2 타입은 any와 다른 타입을 구별 할 수 있지만 인터섹션은 구분 못함

  • Equal2<any, unknown> 의 경우 extends 를 false로 만드는 T가 없음에도 false가 된다.

type R5 = Equal2<any, 1>// false
type R6 = Equal2<{x:1} & {y:2}, {x:1, y:2}>; //false
type R7 = Equal2<any, unknown>; // false

NotEqual

  • Equal 타입의 결과를 반대로 적용

type NotEqual<X, Y> = Equal<X, Y> extends true ? false : true;

Last updated