이 문서에서는 타입 좁히기(Narrowing)와 사용자 정의 타입 가드(User-Defined Type Guards)에 대해 다루고, 이들이 어떻게 TypeScript의 타입 시스템을 더 유연하고 정확하게 만드는지 설명한다.
Background
Union Type (유니온 타입)
유니온 타입은 TypeScript에서 둘 이상의 타입을 하나의 타입으로 결합하여, 해당 값이 여러 타입 중 하나일 수 있음을 나타내는 타입이다. 유니온 타입을 사용할 때는 해당 변수나 매개변수가 가질 수 있는 모든 타입에 공통적으로 존재하는 멤버에만 접근할 수 있다.
즉, 유니온 타입은 다양한 타입을 하나의 변수로 처리할 수 있지만, 이러한 유연성에는 타입 안정성을 유지하기 위한 주의가 필요하다. 아래 예제를 살펴보자.
interface Cat {
name: string;
meow: () => void;
}
interface Dog {
name: string;
bark: () => void;
}
function getAnimal(): Cat | Dog {
// Returns either a 'Cat' or a 'Dog'
}
let animal = getAnimal();
animal.name(); // This is safe because both 'Cat' and 'Dog' have 'name' method
// animal.meow(); // Error: Property 'meow' does not exist on type 'Dog'
- 위 코드에서
getAnimal
함수는Cat
또는Dog
타입을 반환한다. 따라서getAnimal
함수는Cat | Dog
타입을 가지며, 두 타입에 공통적으로 존재하는name
메서드를 호출할 수 있다. 하지만,meow
메서드는Dog
타입에서는 사용할 수 없다.
💬 그렇다면, 특정 타입에만 존재하는 메서드를 사용하기 위한 방법은 무엇일까? 바로 이 문서를 쓰게 된 이유이다.
TypeScript에서 유니온 타입을 사용할 때, 특정 타입의 고유한 속성이나 메서드에 안전하게 접근하기 위해서는 타입을 좁히는 기법이 필요하다. 다음에서는 이러한 타입 좁히기와 사용자 정의 타입 가드가 어떻게 유용하게 활용될 수 있는지 살펴보자.
타입 좁히기
타입 좁히기는 TypeScript에서 변수가 가질 수 있는 타입의 범위를 좁히는 과정이다. TypeScript는 코드를 분석할 때 변수의 타입을 가능한 구체적으로 좁혀서 타입 안정성을 높이려고 한다.
typeof
연산자: 기본 타입(예:string
,number
,boolean
)을 좁히는 데 사용된다.instanceof
연산자: 클래스의 인스턴스인지 확인하여 타입을 좁히는 데 사용된다.in
연산자: 객체의 속성이 존재하는지 확인하여 타입을 좁히는 데 사용된다.- 사용자 정의 타입 가드: 특정 조건을 만족할 때 타입을 좁히기 위해 커스텀 함수를 정의한다.
사용자 정의 타입 가드
우리는 코드 내에서 타입이 어떻게 변하는지를 더 세밀하게 제어하고 싶을 때가 있다. 이때 사용자 정의 타입 가드를 사용한다. 사용자 정의 타입 가드는 특정 조건을 기반으로 타입을 좁히기 위해 직접 정의하는 함수이다.
사용자 정의 타입 가드 정의하기
사용자 정의 타입 가드를 정의하려면, 반환 타입이 타입 서술어(type predicate)인 함수를 작성하면 된다.
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
- 위 함수에서
animal is Cat
이 타입 서술어이다. 타입 서술어는parameterName is Type
의 형태를 가지며, 여기서parameterName
은 현재 함수의 매개변수 이름이어야 한다. - 타입 서술어(
animal is Cat
): 이 서술어는 함수가true
를 반환할 경우, TypeScript에게animal
이Cat
타입이라고 알려준다.
타입 가드를 사용하여 타입 좁히기
TypeScript는 특정 변수의 타입을 함수 호출 결과에 따라 좁혀서 추론한다.
let animal = getAnimal();
if (isCat(animal)) {
animal.meow();
} else {
animal.bark();
}
- 이 코드에서 TypesCript는
isCat(animal)
이true
를 반환할 경우animal
을Cat
타입으로 인식하고meow
메서드를 호출한다. 반대로,false
일 경우에는animal
을Dog
타입으로 인식하고bark
메서드를 호출한다.
배열에서 타입 가드 사용하기
배열에서 특정 타입의 요소만 필터링할 때도 타입 가드를 사용할 수 있다. Cat | Dog
타입의 요소가 있는 배열에서 Cat
타입만을 추출하는 방법은 다음과 같다.
const animals: (Cat | Dog)[] = [getAnimal(), getAnimal(), getAnimal()];
const cats: Cat[] = animals.filter(isCat);
animals
배열에서isCat
타입 가드를 사용하여Cat
타입의 요소만을 필터링한다. 필터링 결과는Cat[]
타입의 새로운 배열이다.
요약
- 타입 좁히기는 변수가 더 구체적인 타입으로 제한되는 과정이다.
- 사용자 정의 타입 가드는 타입 서술어를 사용하여 직접 정의한 함수로, 조건에 따라 타입을 좁힌다.