> For the complete documentation index, see [llms.txt](https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/dev_note/typescript/undefined/undefined-10.md).

# 유용한 타입 만들기

## 판단하는 타입만들기

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

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

### IsNever

* T에 never를 넣을 때 분배 법칙이 일어난것을 막기 위해 배열로 감싸는것을 확인
  * never를 유니온으로 생각, 제너릭과 유니온이 같이 쓰이면 분배 법칙이 발생
  * never에 분배 법칙이 발생하면 never가 된다.

```typescript
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 할 수 있게됨

```typescript
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\[]이 필요

```typescript
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 판별 조건도 여기서 같이 걸러짐

```typescript
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가 된다.

```typescript
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 } 이 나와야 한다.

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

```typescript
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)를 제거하는 타입

{% hint style="info" %}
타입스크립트에서 부분집합이란?&#x20;

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

```typescript
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는 서로 동일하다는 듯

```typescript
type Equal<A, B> = A extends B ? B extends A ? true : false : false;
```

* 위 코드는 허점이 있음, true로 예상했으나 그렇지 않은 사레들이 존재

```typescript
type R1 = Equala<boolean, true | false> // boolean
type R2 = Equal<never, never> // never
```

* 유니온이 일어나는 분배법칙 때문 (never도 유니온이라 생각)
  * 분배법칙이 일어나지 않도록 배열로 감싸기

```typescript
type Equal<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false
```

* 단 위 Equal 타입은 any를 구분할 수 없다.

```typescript
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가 된다.

```typescript
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 타입의 결과를 반대로 적용

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