# 유용한 타입 만들기

## 판단하는 타입만들기

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

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

### 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;
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/dev_note/typescript/undefined/undefined-10.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
