# 컨디셔널 타입

### 핵심 포인트

* 컨디셔널 타입에 **`never`**&#xB97C; 사용할 때가 많은데 **제너릭과 같이 쓸 때에만 의미**가 있다.
* 매핑된 객체 타입에서 키가 **`never`** 이면 해당 속성은 제거된다.
* 검사하려는 타입이 **제너릭**이면서 **유니언**이면 **분배법칙이 실행됨**
* **boolean**은 분배법칙이 일어나면 **`true | false`** 로 인식하게 된다.
* 분배법칙을 막고 싶다면 배열로 감싸라&#x20;
* never도 분배법칙의 대상이 된다. -> never는 유니언으로 보이진 않지만 **유니언으로 생각하는것이 좋다.**
  * never는 분배법칙이 일어나면 never 가 된다.
  * <mark style="color:red;">**간단하게 제너릭과 never가 만나면 never가 된다고 생각하자.**</mark>
  * never를 타입인수로 사용할 때는 분배법칙을 막기 위해 배열로 감싸라
* 타입스크립트는 <mark style="color:red;">**제너릭이 들어있는 컨디셔널 타입을 판단할 때 값의 판단을 뒤로 미룬다.**</mark>
  * 이 때도 타입스크립트가 판단을 뒤로 미루지 못하도록 배열로 제너릭을 감싸면 된다.

## 컨디셔널 타입

> **조건에 따라 다른 타입**이되는 컨디셔널 타입

```typescript
type A1 = string;
type B1 = A1 extends string ? number : boolean; // type B1 = number

type A2 = number;
type B2 = A2 extends string ? number : boolean; // type B2 = boolean
```

* **`extends`** 연산자가 **삼항연산자**와 같이 사용됨
  * **`특정 타입 extends 다른 타입 ? 참일 때 타입 : 거짓일 때 타입`**
* 특정 타입이 다른 타입의 부분집합일 때 참
* 컨디셔널 타입은 타입 검사를 위해서도 많이 사용함

### 컨디셔널 타입은 **`never`**&#xC640; 함께 사용할 때 도 많다.

> <mark style="color:purple;">**보통은 제너릭과 더불어 쓸 때만**</mark><mark style="color:purple;">**&#x20;**</mark><mark style="color:purple;">**`never`**</mark><mark style="color:purple;">**가 의미가 있다.**</mark>

```typescript
type Start = string | number;
type New = Start extends string | number ? Start[] : never;
let n: New = ['hi'];
n = [123];
```

* ❓ 그냥 `type New = Start[]` 로 사용해도 되지 않나??
  * 사실 그렇다.
  * **단순한 상황에서는 `never` 와 함께 쓸 이유가 없다.**&#x20;
  * <mark style="color:purple;">**보통은 제너릭과 더불어 쓸 때만**</mark><mark style="color:purple;">**&#x20;**</mark><mark style="color:purple;">**`never`**</mark><mark style="color:purple;">**가 의미가 있다.**</mark>

```typescript
type ChooseArray<A> = A extends string ? string[] : never;
type StringArray = ChooseArray<string>; // type StringARray = string[]
type Never = ChoosArray<number>; // type Never = never;
```

{% hint style="info" %}
**`never`** 타입은 모든 타입에 대입할 수 있기에 **모든 타입을 extends 할 수 있다.**

```typescript
type Result = never extends string ? true : false; // type Result = true
```

{% endhint %}

### 매핑된 객체 타입에서 키가 **`never`** 이면 해당 속성은 제거된다.

```typescript
type OmitByType<O, T> = {
  [K in keyof O as O[K] extends T ? never : K]: O[K];
}
type Result = OmitByType<{ // type Result = { name: string, age: number }
  name: string;
  age: number;
  married: boolean;
  rich: boolean;
}, boolean>
```

### 중첩 삼항연산자

> 컨디셔널 타입은 자바스크립트 삼항연산자처럼 중첩해서 만들기 가능

```typescript
type ChooseArray<A> = A extends string 
  ? string[]
  : A extends boolean ? boolean[] : never;
type StringArray = ChooseArray<string>; // string[]
type BooleanArray = ChooseArray<boolean>; // boolean[]
type Never = ChooseArray<number>; // never
```

* 인덱스 접근 타입으로 컨디셔널 타입을 표현 가능

```typescript
type A1 = string;
// B1 타입과 B2 타입은 같음
type B1 = A1 extends string ? number : boolean;
// B2 처럼 왜 굳이 복잡하게 사용하냐면, 참일 때와 거짓일 때의 타입이 복잡한 경우는 아래처럼 나타내기도 함
type B2 = {
  't': number;
  'f': boolean;
}[A1 extends string ? 't' : 'f']
```

### 분배 법칙

> 제너릭과 never의 조합은 더 복잡한 상황에서 진가를 발휘함

* string | number 타입으로부터 string\[] 타입을 얻고 싶을 때?

```typescript
type Start = string | number;
// string | number 가 string을 extends 할 수 없기 때문
// string이 더 구체적이므로 대입 불가능 
type Result = Start extends string ? Start[] : never; // never

// 컨디셔널 타입을 제너릭과 함께 사용하면 원하는 동작 가능
type Start = string | number;
type Result<Key> = Key extends string ? Key[] : never;
let n: Result<Start> = ['hi']; // let n: string[]
```

* 검사하려는 타입이 **제너릭**이면서 **유니언**이면 **분배법칙이 실행됨**
  * `Result<string | number>`는 Result\<string> | Result\<number> 가 된다.
  * 따라서 `Key extends string | boolean ? Key[] : never`를 거치면서 string \[] | never가 되고 never는 사라져서  최종적으로 string\[] 타입이 된 것
* <mark style="color:red;">**`boolean`**</mark><mark style="color:red;">에 분배법칙이 적용될 떄는 조심!!</mark>
  * boolean을 true | false 로 인식하게 된다.

```typescript
type Start = string | number | boolean;
type Result<Key> = Key extends string | boolean ? Key[] : never;
let n: Result<Strart> = ['hi']; // string[] | false[] | true[]
n = [true];
```

### 분배법칙을 막고 싶다면?

> <mark style="color:red;">**배열로 감싸면 분배법칙이 일어나지 않는다.**</mark>

```typescript
type IsString<T> = T extends string ? true : false;
// 분배법칙에 의해 IsString<'h1'> | IsString<3> -> true | false => boolean
type Result = IsString<'h1' | 3>; // type Result = boolean

// 배열로 감싼 경우
type IsString<T> = [T] extends [string] ? true : false;
type Result = IsString<'h1' | 3>; //  type Result = false
```

### never도 분배법칙의 대상이 된다.

> **never**가 유니언으로 보이지는 않지만 **유니언으로 생각하는것이 좋다.**
>
> **never가 분배법칙이 일어나면 never가 된다. (공집합에서 분배법칙이 일어나면 아무것도 실행하지 않기때문)**

```typescript
type R<T> = T extends string ? true : false;
// 분배법칙이 일어나서 true가 아니라 never가 됨
type RR = R<never>; // type RR = never
```

* **`never`는 공집합과 같으므로** **공집합에서 분배법칙을 실행하는 것은 아무것도 실행하지 않는것과 같다.**
* <mark style="color:red;">**간단하게 제너릭과 never가 만나면 never가 된다고 생각하자.**</mark>
* <mark style="color:blue;">**따라서 never를 타입인수로 사용하려면 분배법칙이 일어나는것을 막아야 한다.**</mark>

```typescript
type IsNever<T> = [T] extends [never] ? true : false;
type T = isNever<never>; // Type T = true
type F = isNever<'never'>; // Type F = false
```

* 같은 이유로 제너릭과 컨디셔널 타입을 같이 사용할 때는 다음을 조심하자.
  * 타입스크립트는 <mark style="color:red;">**제너릭이 들어있는 컨디셔널 타입을 판단할 때 값의 판단을 뒤로 미룬다.**</mark>
  * 이 때도 타입스크립트가 판단을 뒤로 미루지 못하도록 배열로 제너릭을 감싸면 된다.

```typescript
function test<T>(a: T) {
  type R<T> = T extends string ? T : T; // R<T> 타입이 T 타입이 될거라고 생각하는게 잘못됨
  // 즉, 변수 b에 매개변수 a를 대입할 때까지도 타입스크립트는 R<T>가 T 라는것을 알지 못한다.
  const b: R<T> = a; // type 'T' is not assignable to type 'R<T>'
}

// 제너릭을 배열로 감싸면 타입 판단을 뒤로 미루지 않게됨
// 타입 매개변수를 선언할 때 바로 <[T] extends [string]> 하는것이 불가능하므로
// 한 번 더 컨디셔널 타입으로 묶어서 선언한 것
function test<T extends ([T] extends [string] ? string : never)>(a: T) {
  type R<T> = [T] extends [string] ? T : T;
  const b: R<T> = a;
}
```

{% hint style="success" %}
컨디셔널 타입은  **`infer`**&#xB97C;  통해 더 강력하게 사용 가능
{% endhint %}
