[TypeScript 공식문서] 3. Narrowing

2025. 3. 25. 00:25·Web/TypeScript

https://www.typescriptlang.org/docs/handbook/2/narrowing.html

  • 타입 좁히기 : 유니언 타입 중에서 실제로 어떤 타입인지를 코드 흐름에 따라 좁혀서 사용하는 것

1. typeof 타입 가드

  • JS 에서는 typeof 연산자를 사용해서 값의 타입을 문자열로 확인가능
typeof "hello"   // "string"
typeof 123       // "number"
typeof true      // "boolean"
typeof undefined // "undefined"
typeof Symbol()  // "symbol"
typeof 123n      // "bigint"
typeof {}        // "object"
typeof function() {} // "function"

예제 : typeof 타입가드

function padLeft(padding: number | string, input: string): string {
  if (typeof padding === "number") {
    return " ".repeat(padding) + input;
  }
  return padding + input;
}
  • typeof padding === "number" 때문에, padding 의 타입이 number로 좁혀짐
  • repeat(padding) 처럼 number 메서드를 안전하게 사용가능

주의 : typeof null === “object”

  • JS에서 null은 object로 판단됨
typeof null; //"object"
  • 이건 역사적인 에러임, 따라서 typeof 만으로는 null을 완전히 구분할 수 없다.
function printAll(strs: string | string[] | null) {
  if (typeof strs === "object") {
    for (const s of strs) {
// 'strs' is possibly 'null'.
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  } else {
    // do nothing
  }
}
  • strs가 null 일 수도 있기 때문에, TypeScript는 에러를 표시함
  • 이를 해결하려면 truthy 체크를 추가해야함

2. Truthiness narrowing

  • JavaScript에서는 if , && , || 등의 조건문에서 모든 값이 boolean으로 자동 변환돼서 판단됨.
  • 이걸 이용해서 타입을 좁히는 것을Truthiness Narrowing 이라함

Truthy, Falsy 값이란?

자바스크립트에서 조건문에 사용될 때 false로 취급되는 값들(falsy):

  • false
  • 0
  • "" (빈 문자열)
  • NaN
  • null
  • undefined

이 이외의 값은 모두 truthy 로 간주됨

Boolean 강제 변환

Boolean("hello"); // true
Boolean("")       // false
!!"world";        // true (boolean으로 강제 변환)
function printAll(strs: string | string[] | null) {
  if (strs && typeof strs === "object") {
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  }
}
  • strs && typeof strs === "object”
    • null 을 먼저 걸러냄(strs가 trurhy인지 확인)
    • 그 다음 typeof strs === "object” → string[] 으로 좁혀짐

주의 : 빈 문자열은 falsy

function printAll(strs: string | string[] | null) {
  if (strs) {
    if (typeof strs === "object") {
      for (const s of strs) {
        console.log(s);
      }
    } else if (typeof strs === "string") {
      console.log(strs);
    }
  }
}
  • if(strs) 에서 빈문자열 “”도 걸러져서 출력이 안 될 수 있다.

3. Equality narrowing

  • JavaScript/TypeScript에서 ===, !==, ==, != 같은 비교 연산자를 통해

값이 같은지를 비교하면서 타입을 좁힐 수 있다.

function example(x: string | number, y: string | boolean) {
  if (x === y) {
    x.toUpperCase(); // string으로 좁혀짐
  }
}

느슨한 비교(!=)를 이용해 null과 undefined를 한번에 체크할 수도 있다.

function multiplyValue(container: { value: number | null | undefined }) {
  if (container.value != null) {
    container.value *= 10;
  }
}

4. in 연산자 기반 좁히기

객체의 속성 존재 여부를 체크해서 타입을 좁힐 수 있다.

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

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

5. instanceof 기반 좁히기

클래스의 인스턴스 여부로 타입을 좁힐 수 있다.

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

6. 사용자 정의 타입 가드 (Custom Type Guard)

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

const pet = getSmallPet();
if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}

7. Discriminated Union (구분자 유니언)

공통된 속성을 활용하여 타입을 구분하는 패턴이다.

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;
  }
}

8. never 타입과 Exhaustiveness 체크

never는 모든 타입의 값을 허용하지 않는 특수 타입이다. 모든 케이스를 처리했는지 확인할 때 사용된다.

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;
  }
}

위 코드에서 Shape 타입에 새로운 멤버를 추가하면, 해당 케이스를 처리하지 않는 이상 컴파일 에러가 발생한다.

'Web > TypeScript' 카테고리의 다른 글

[TypeScript 공식문서] 2. Everyday Types(2)  (1) 2025.03.22
[TypeScript 공식문서] 2. Everyday Types(1)  (1) 2025.03.22
[TypeScript 공식문서] 1. The Basics  (1) 2025.03.20
'Web/TypeScript' 카테고리의 다른 글
  • [TypeScript 공식문서] 2. Everyday Types(2)
  • [TypeScript 공식문서] 2. Everyday Types(1)
  • [TypeScript 공식문서] 1. The Basics
bernie
bernie
공부한 내용을 업로드합니다.
  • bernie
    Daegi Bernie Yeo
    bernie
  • 전체
    오늘
    어제
    • 분류 전체보기 (11)
      • Web (9)
        • JavaScript (4)
        • TypeScript (4)
      • Algorithm (2)
      • CS (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    react
    input
    리액트
    백준
    mordan cpp
    괄호 표기법
    javascript
    속성 접근자
    SSR
    onKeydown
    cpp
    딥다이브
    자바스크립트
    점 표기법
    BFS
    CSR
    Event
    미로탐색
    typescript
    c++
    객체리터럴
    객체
    js
    2178
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
bernie
[TypeScript 공식문서] 3. Narrowing
상단으로

티스토리툴바