본문 바로가기
front/ts

Type Narrowing

by juniKang 2022. 7. 18.

1. typeof type guards

typeof operator를 사용하여 narrowing(좁히기) 가능.

function printAll(strs: string | string[]) {
  if (typeof strs === "object") {
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  } else {
    // do nothing
  }
}

자바스크립트에서 typeof null === "object" 는 true이다.

 

2. truthiness narrowing

0, NaN, "", 0n, null, undefined를 체크할 수 있다.

function truthiness(target: unknown) {
  return target ? true : false; 
}

// all false
truthiness(0);
truthiness(NaN);
truthiness("");
truthiness(0n);
truthiness(null);
truthiness(undefined);

주의할 점은, ""와 0을 false로 체크하기 때문에 주의해야한다. 

 

3. Equality narrowing

아래 예제처럼, 타입이 string일 때만 true절을 탈 수 있으므로, true 절 안에서 x, y는 string이라고 타입스크립트가 체크해 줄 수 있다.

function example(x: string | number, y: string | boolean) {
  if (x === y) {
    x // string
  } else {
    x // string | number
    y // string | boolean
  }
}

 

4. The in operator narrowing

in 오퍼레이터를 사용해서 체크할 수 있다.

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

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    return animal.swim(); // animal is Fish
  }
 
  return animal.fly(); // animal is Bird
}

 

5. instanceof narrowing

function logValue(x: Date | string) {
  if (x instanceof Date) {
    console.log(x.toUTCString()); // x is Date
  } else {
    console.log(x.toUpperCase()); // x is string
  }
}

 

6. Assignments

값을 할당할 때, 타입스크립트는 적절하게 추론하여 타입을 매긴다. x변수는 string | number 타입을 가질 수 있다고 할당했다. 그러나 다른 타입이 들어오면 error를 나타낸다.

let x = Math.random() < 0.5 ? 10 : "hello world!"; // x: string | number
x = 1; // x: number
x = "goodbye!"; // x: string
x = true // ❌Error: Type 'boolean' is not assignable to type 'string | number'.

 

7.  Control flow analysis

조건문을 통과하는 흐름을 분석해서, 타입을 유추한다.

function example() {
  let x: string | number | boolean;
  x = Math.random() < 0.5;
  console.log(x); // x: boolean
  if (Math.random() < 0.5) {
    x = "hello";
    console.log(x); // x: string
  } else {
    x = 100;
    console.log(x); // x: number
  }
  return x; // x: string | number
}

 

8. Using type predicates

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

function isFish(pet: Pet): pet is Fish {
  return (pet ad Fish).swim !== undefined;
}

const zoo: Pet[] = [...pets];
const underWater1: Fish[] = zoo.filter(isFish);
const underWater2: Fish[] = zoo.filter((pet): pet is Fish => {
  if (pet.name === "sharkey") return false;
  return isFish(pet);
});

 

9. Discriminated unions

interface Circle {
  kind: "circle";
  radius: number;
}
 
interface Square {
  kind: "square";
  sideLength: number;
}
 
type Shape = Circle | Square;
function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
  }
}

 

10. The never type

좁힐 때, 모든 가능한 타입을 없애면, 타입스크립트는 never 타입을 사용한다.

 

11. Exhaustiveness checking

type Shape = Circle | Square;
 
function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
    default:
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

댓글