반응형
250x250
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

브랜드 속성 & 타입 좁히기 본문

TS/TS(with ZeroCho)

브랜드 속성 & 타입 좁히기

5_hyun 2023. 11. 12. 20:02

브랜드 속성

브랜드 속성은 객체를 구별할 수 있는 속성을 하나 추가하는 방법이다.

예시

interface Money {
    __type: 'money';
    amount: number;
    unit: string;
}

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

이 코드에서는  "__type" 이 속성을 브랜드 속성이라고 한다.

속성 이름은 다른 속성과 겹치지 않는 이름이면 다 가능하다. 이렇게 브랜드 속성을 사용하는 것을 브랜딩이라고 한다.


타입 좁히기

TS에서 타입을 구분하는 것은 중요하다.

대부분은 TS가 자체적으로 코드를 파악해서 타입을 추론하는 제어 흐름 분석(Control Flow Analysis)을 한다. 하지만 제어 흐름 분석이 완벽하지 않다.

 

-제어 흐름 분석이 부정확한 예시

function strOrNullOrUndefined(param: string | null | undefined) {
    if (typeof param === 'undefined') {
    	param; // undefined
     } else if (param) {
     	param; // string
     } else {
     	param; // string | null
     }

이렇게 마지막 else 문에서 string이 걸러지지 않고 나온다.

그 이유는 JS의 유명한 버그인 "type of null === 'object'"라는 점 때문이다. 그렇기에 객체와 null의 typeof 결과가 같아서 typeof로 null을 구분할 수 없다.

 

-올바르게 하는 방법

function strOrNullOrUndefined(param: string | null | undefined) {
    if (param === undefined) {
    	param; // undefined
     } else if (param === null) {
     	param; // null
     } else {
     	param; // string
     }

이렇게 JS 문법을 사용하면 쉽게 구분할 수 있다.

명시적으로 유니언인 타입이 아니어도 타입 구분이 가능

function tureOrFalse(param: string) {
    if (param) {
      param; // true
    } else {
      param; // false
    }
}

boolean은 true | false이니까 이렇게 구분도 가능하다.

배열을 구분하기

function example(param: string | number[]) {
  if (Array.isArray(param)) {
    param;
  } else {
    param;
  }
}

Array.isArray 메서드를 사용해서 구분할 수 있다.

클래스 구분하기

class A {}
class B {}

function example(param: A | B) {
  if (param instanceof A) {
    param; // A
  } else {
    param; // B
  }
}

instanceof 연산자를 사용하여 구분할 수 있다. 

함수 구분하기

-에러 발생 코드

interface X {
  width: number;
  height: number;
}
interface Y {
  length: number;
  center: number;
}

function objXorY(param: X | Y) {
  if (param instanceof X) {
    param; 
  } else {
    param;
  }
}

JS에서 함수는 객체이다. 따라서 위 코드는 2개의 객체를 구분하는 방식이다.

클래스처럼 instanceof를 사용하여 구분할 수 있지만 에러가 발생했다. 그 이유는 instance가 JS 문법이 아니기 때문이다.

즉, 타입 좁히기는 JS 문법을 사용해서 진행해야 한다. 왜냐하면 JS에서도 실행할 수 있어야 하는 코드여야 하기 때문이다.

 

-올바른 코드1

interface X {
  width: number;
  height: number;
}
interface Y {
  length: number;
  center: number;
}

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

in 연산자를 사용하면 잘 구분할 수 있다.

 

-올바른 코드2

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;
  } else {
    param;
  }
}

혹은 브랜드 속성을 사용하면 객체 구분이 쉬워진다.

타입 좁히는 함수를 만들기(타입 서술 함수)

-잘못된 코드

function isMoney(param: Money | Liter) {
  if (param.__type === 'money') {
    return true; // true 리턴할 경우 Money 값으로 좁혀짐
  } else {
    return false;
  }
}
function moneyOrLiter(param: Money | Liter) {
  if (isMoney(param)) {
    param; // Money | Liter
  } else {
    param; // Money | Liter
  }
}

param이 Money인지 Liter인지 판단하는 isMoney 함수를 만들었다.

하지만 타입을 제대로 구분하지 못하고 있다. 이럴때는 isMoney 함수에 특수한 작업을 해줘야 한다.

 

-올바른 코드

function isMoney(param: Money | Liter): param is Money {
  if (param.__type === 'money') {
    return true; // true 리턴할 경우 Money 값으로 좁혀짐
  } else {
    return false;
  }
}
function moneyOrLiter(param: Money | Liter) {
  if (isMoney(param)) {
    param; // Money
  } else {
    param; // Liter
  }
}

isMoney 함수에 반환값 타입으로 param is Money라는 타입 표시를 했다. 이를 타입 서술 함수(Type Predicate)라고 한다.

Predicate매개변수 하나를 받아 boolean을 반환하는 함수를 의미한다.

=> param is Money으로 하면, isMoney 함수의 반환값이 true일 때 매개변수의 타입도 is 뒤에 적은 타입으로 좁혀진다.

 

하지만 타입 서술 합수는 위에서 다뤘던 기본적인 좁히기를 먼저하고 나서, 정 안 될 때 사용하는 것이 좋다.

728x90
반응형
Comments