티스토리 뷰

 

[ Narrowing ]

타입스크립트에서 함수 내부 또는 순차적으로 진행되는 로직 안에서 유니언 타입으로 여러 멤버 타입을 받게 되면 프로그램에서 해당 값을 처리할 때 오류가 나지 않게 Narrowing(좁히기) 처리를 해주어야합니다.

해당 내용은 타입스크립트 문서를 통해서 공부를 진행 했으며 다음 링크를 참고하시면 됩니다.

레퍼런스: https://www.typescriptlang.org/docs/handbook/2/narrowing.html

 

Documentation - Narrowing

Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects.

www.typescriptlang.org

 

유니언 타입에서의 대처

다음과 같이 매개변수에서 유니언 타입을 받는 함수가 있습니다. 이 함수의 parseInt의 인자로 num 값을 넘겨주면 오류가 납니다. parseInt의 첫번째 인자는 string 타입만을 받기 때문입니다.

function stringNumParse(num: string | number):number {
  return parseInt(num, 10);
  // Argument of type 'string | number' is not assignable to parameter of type 
  // 'string'. Type 'number' is not assignable to type 'string'.
}

 

이때 typeof 를 통해 해당 코드가 실행되는 범위를 좁혀주어 해결할 수 있습니다.

function stringNumParse(num: string | number): number {
  if (typeof num === "string") {
    return parseInt(num, 10);
    // num type : string
  } else {
    return num;
    // num type : number
  }
}

 

 

 

if 문과 typeof 로 이렇게 코드의 범위를 좁혀주는 형태는 자바스크립트의 코드를 짤 때 많이 보던 패턴입니다. 타입스크립트는 이러한 자바스크립트의 코드 스타일 방식을 최대한 침해하지 않으며 타입을 제공하고 있습니다. 구체적으로 타입의 유형을 축소시키는 검사를 타입 가드(type gaurd)라고 합니다.

 

[ typeof ]

자바스크립트의 연산자 typeof 를 통해서 타입 가드가 가능합니다. 해당 값에 (typeof {값} === 타입) 의 분기를 통해서 각 코드의 범위를 좁힐 수 있습니다.

 

자바스크립트의 typeof를 통해서 타입 가드를 할 수 있는 유형들

- "string" , "number" , "bigint" , "boolean" , "symbol" , "undefined" , "object" , "function"

function typeGuard(
  value:
    | string
    | number
    | bigint
    | boolean
    | symbol
    | undefined
    | object
    | Function
) {
  if (typeof value === "string") {
    // ...
  } else if (typeof value === "number") {
    // ...
  } else if (typeof value === "bigint") {
    // ...
  } else if (typeof value === "boolean") {
    // ...
  } else if (typeof value === "symbol") {
    // ...
  } else if (typeof value === "undefined") {
    // ...
  } else if (typeof value === "object") {
    // ...
  } else if (typeof value === "function") {
    // ...
  }
}

 

 

[ 진실성 축소 (Truthiness narrowing) ]

자바스크립트의 조건부인 &&, ||, ! 등의 표현식을 사용해서 타입 가드를 해줄 수 있습니다.

function numberOfPeople(number: number) {
  if (number) {
    // number 가 0 이 아닐 때
    // 자바스크립트에서 숫자 0은 if 문에서 false와 같이 동작하고 나머지는 true와 같이 동작합니다.
    // ...
  } else {
    // number 가 0 일 때 동작
    // ...
  }
}

 

if 조건문에서 false와 같이 동작하는 값들

  • 0
  • NaN
  • " " (빈 문자열)
  • 0n (bigint 제로 버전)
  • null
  • undefined

boolean 과 같이 동작하기

강제로 타입이 다른 값을 boolean 과 같이 동작하게 하는 방법이 있습니다.

자바스크립트의 Boolean 이나 !! 연산자를 사용하면 됩니다.

function numberOfPeople(number: number) {
  if (Boolean(number)) {
    //...
  } else {
    //...
  }
}

// or

function numberOfPeople(number: number) {
  if (!!number) {
    //...
  } else {
    //...
  }
}

 

진실성 축소를 통해서 값의 존재 여부 판단 후 로직 실행

function debug(strs: string | string[]) {
  // && 연산자와 함께 진실성을 체크하여 코드 범위를 축소합니다.
  if (strs && typeof strs === "object") {
    for (const st of strs) {
      console.log(st);
    }
  }
  // ...
}

 

등식 축소(Equality narrowing)

타입스크립트는 자바스크립트의 동등성 검사를 할 수 있는 ===, !==, ==, != 와 같은 연산자를 통하여 타입 가드를 할 수 있습니다.

아래에서는 === 을 통해서 두 값이 같은지 판별하여 두 값이 같을 땐 string 타입일 때 밖에 없으므로 코드 범위를 축소 시킬 수 있습니다.

function debug(
  numOrString: string | number,
  stringOrBoolean: string | boolean
) {
  if (numOrString === stringOrBoolean) {
    numOrString.toUpperCase();
    // type : (method) String.toUpperCase(): string
    stringOrBoolean.toUpperCase();
    // type : (method) String.toUpperCase(): string
  } else {
    console.log(numOrString);
    // type : (parameter) numOrString: string | number
    console.log(stringOrBoolean);
    // type : (parameter) stringOrBoolean: string | boolean
  }
}

 

동등성 연산자를 통해서 다음과 같이 null 이 들어올 수 있는 상황에서 대처를 할 수 있습니다.

function debug(strs: string | string[] | null) {
  // strs 가 null이 아닐때만 코드 로직이 실행 될 수 있도록 하였습니다.
  if (strs !== null) {
    /* 
    if(typeof strs === "???"){
      //...
    }
    */
  }
}

 

in 연산자 축소( in operator narrowing)

자바스크립트의 객체에 프로퍼티가 있는지 판별하는 in 연산자를 통해서 타입 축소도 가능합니다.

type Fish = { swim: () => {} };
type Bird = { fly: () => {} };

function action(animals: Fish | Bird) {
  if ("fly" in animals) {
    return animals.fly();
  } else {
    return animals.swim();
  }
}

 

instanceof 축소

해당 값이 다른 값의 인스턴스인지 확인하는 자바스크립트의 instanceof 를 통해서 타입 가드로 코드 범위를 좁힐 수 있습니다.

interface Car {
  name: string;
  age: number;
  move: () => void;
}

class SpecialCar {
  constructor() {
    // ...
  }
  fly() {}
}

function carCheck(cars: Car | SpecialCar) {
  if (cars instanceof SpecialCar) {
    // ....
    cars.fly();
    // (parameter) cars: SpecialCar
  } else {
    // ...
    cars.move();
    // (parameter) cars: Car
  }
}

 

타입 명제 (type predicates)

타입이 어떤 타입을 반환하는지 체크하는 함수를 타입스크립트를 통해서 만들 수 있습니다.

다음과 같이 일반 유저와 특별한 유저를 반환하는 함수가 있습니다.

interface User {
  name: string;
}

interface SpecialUser extends User {
  specialKey: number;
}

function getUser(): User | SpecialUser {
  return { name: "Tom", specialKey: 10 };
}

let user = getUser();

반환된 user 변수를 반환된 형식에 맞춰 로직을 실행 할 때 함수를 만들어 주면 되지만 명시적으로 타입스크립트에서 어떤 값이 반환될 것인지를 지정해 줄 수 있습니다. is 를 통해서 반환될 값이 어떤 값일지 명시적으로 표시해줍니다.

function isSpecialUser(user: User | SpecialUser): user is SpecialUser {
  return (user as SpecialUser).specialKey !== undefined;
}

// ...

let user = getUser();

if (isSpecialUser(user)) {
  console.log(user.specialKey);
  // type : SpecialUser
} else {
  console.log(user.name);
  // type : User
}

 

구별된 유니온( Discriminated unions )

유니온 타입을 하나의 프로퍼티에서 사용하지 않고 구별된 인터페이스를 통해서 사용합니다.

//...
interface SpecialUser extends UserInfo {
  type: "special";
  specialKey: number;
}

interface NormalUser extends UserInfo {
  type: "normal";
}

type User = SpecialUser | NormalUser;

이를 바탕으로 타입을 좁혀주어 코드를 처리해줍니다.

function userCheck(user: User) {
  if (user.type === "special") {
    //... special 유저인 경우 처리
  } else {
    //... 나머지 유저 인경우 처리
  }
}

 

그렇다면 나머지 유저인 경우에서도 처리를 명시적으로 하고 하지 않았을 때에 오류를 내보내주고 싶을땐 어떻게 할까요?

function userCheck(user: User) {
  if (user.type === "special") {
    //... special 유저인 경우 처리
  } else {
    //... 나머지 유저 인경우 처리
  }
  // normal 유저도 처리를 명시적으로 하고 싶습니다.
}

 

철저한 검사 (Exhaustiveness checking)

다음과 같이 never을 사용해서 나머지에서도 완전한 타입 체크를 하는 상황을 발생시키면 예외인 처리를 해줄 수 있습니다.

type User = SpecialUser | StoreUser | NormalUser;

function userCheck(user: User) {
  if (user.type === "special") {
    //... special 유저인 경우 처리
  } else if (user.type === "store") {
    //... store 유저인 경우 처리
  } else if (user.type === "normal") {
    // ... normal 유저인 경우 처리
  } else {
    const _exhaustiveCheck: never = user;
  }
}

 

 

switch 문으로도 동작합니다.

function userCheck(user: User) {
  switch (user.type) {
    case "special":
      // ...
      return user.specialKey;
    case "store":
      // ...
      return user.storeName;
    case "normal":
      //
      return user.name;
    default:
      const _exhaustiveCheck: never = user;
      return _exhaustiveCheck;
  }
}

 

댓글
최근에 올라온 글