# 타입 좁히기

## 타입 좁히기

> 타입스크립트가 **코드를 파악해서 타입을 추론**하는 것을 **제어 흐름 분석**이라 부른다.

* 제어 흐름 분석은 **완벽하지 않다.**
* 다양한 타입 좁히기 방법을 익혀서 사용할줄 알아야 한다.
* <mark style="color:red;">**타입 좁히기는 자바스크립트 문법을 사용해서 진행해야 한다.**</mark>

```typescript
// 간단한 null과 undefined 구분
function strOrNullOrUndefined(param: string | null | undefined) {
  if (param === undefined) { // 또는 typeof param === 'undefined'
    param; // undefined
  } else if (param === null) {
    param; // null
  } else {
    param; // string
  }
}
```

* 타입 좁히기에 꼭 `typeof` 를 써야 할 필요가 없다.
* 타입스크립트도 자바스크립트 문법을 사용하기 때문에 활용

```typescript
// Array 구분
function strOrNumArr(param: string | number[]) {
  if (Array.isArray(param)) {
    param; // number[]
  } else {
    param; // string
  }
}

// instanceof 연산자 활용 (클래스, 함수)
class A {}
class B {}
function classAorB (param: A | B) {
  if (param instanceof A) {
    param; // A
  } else {
    param; // B
  }
}
```

### 두 객체를 구분하는 방법

* 타입 좁히기는 자바스크립트 문법을 사용해서 진행해야 한다.
* **즉, 자바스크립트에서도 실행할 수 있는 코드여야 한다.**

```typescript
interface X {
  width: number;
  height: number
}
interface Y {
  length: number;
  center: number;
}
// X, Y는 타입스크립트의 인터페이스이므로 에러가 발생
function objXorY(param: X | Y) {
  if (param instanceof X) {
    param; // error
  } else {
    param;
  }
}
```

* 객체의 속성으로 구분하면 될까?
* **`in`** 연산자를 사용

```typescript
function objXorY(param: X | Y) {
// 자바스크립트에서 유효한 방식이지만, width 속성이 Y에 존재하지 않지만 width 속성에 접근해서 에러 발생
  if (param.width) { // error
    param;
  } else {
    param;
  }
}

// 👍
function objXorY(param: X | Y) {
  if ('width' in param) {
    param; // X
  } else {
    param; // Y
  }
}
```

### 브랜드 속성 활용

* 브랜드 속성이라는 **공통 속성이 있으므로 in 연산자 대신 바로 속성에 접근 가능**

```typescript
interface Money {
  _type: 'money';
  amount: number;
  unit: string;
}

interface Liter {
  _type: 'liter';
  amount: number;
  unit: string;
}

function moneyOrLiter(param: Money | Liter) {
  if (param._type === 'money') {
    param; // Money
  } else {
    param; // Liter
  }
}
```

### 직접 타입 좁히기 함수 만들어서 사용 (타입 가드)

* if 문에서 사용하는 함수를 직접 만들면 타입 좁히기가 정상 작동하지 않음
  * 논리적으로는 타입이 구분되어야 하지만 타입스크립트는 구분하지 못한다.

```typescript
function isMoney(param: Money | Liter) {
  if (param._type === 'money') {
    return true
  } else {
    return false
  }
}

function moneyOrLiter(param: Money | Liter) {
  if (isMoney(param)){
    param; // Money | Liter
  } else {
    param; // Money | Liter
  }
}
```

* 타입스크립트가 이해할 수 있게 **`타입 서술 함수`** 를 추가로 작업해주어야 한다.
* `is` 특수한 연산자를 사용하면 참일 때 매개변수의 타입도 is 뒤에 적은  타입으로 좁혀진다.
  * 좁혀질 타입을 잘못 적는 경우가 생길 수 있으니 주의
* 최대한 기본적인 타입 좁히기를 먼저 시도하고, 정 안되면 타입 서술을 사

```typescript
function isMoney(param: Money | Liter): param is Money {
  if (param._type === 'money') {
    return true
  } else {
    return false
  }
}
```

{% hint style="info" %}
**Predicate**는 매개변수 하나를 받아 boolean을 반환하는 함수를 의미한다.
{% endhint %}
