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 |