질문 배경:
TypeScript의 발전에 따라, 논리적 분기에서 변수의 더 좁은 타입을 신뢰성 있게 정의해야 하는 문제가 대두되었습니다. 전통적인 타입 검사(예: typeof 또는 instanceof)는 항상 충분하지 않으며, 특히 객체의 구조나 계층이 복잡한 경우 더욱 그렇습니다. 데이터를 안전하게 관리하고 편의성을 높이기 위해, TypeScript는 사용자 정의 타입 가드를 만들기 위한 타입 프레디케이트 메커니즘을 구현했습니다.
문제:
기본적인 타입 검사는 함수 내에서 변수의 타입에 대한 정보를 컴파일러에 제공하지 않으며, true/false 결과만으로는 충분하지 않습니다. 컴파일러는 무엇이 검사되었는지 "이해하지 못합니다". 이는 존재하지 않는 속성에 잘못 접근할 때 런타임에서 암묵적인 오류를 초래합니다.
해결책:
타입 프레디케이트(type predicates)는 'param is Type'과 같은 타입 대체를 통해 컴파일러가 해당 매개변수를 검사를 호출한 후 특정 타입으로 작업할 수 있음을 이해하도록 합니다. 이러한 함수는 타입 안전성을 높이고, 복잡한 문제에 대한 타입 축소 시스템을 확장합니다.
코드 예시:
interface Bird { fly(): void; feathers: boolean; } interface Fish { swim(): void; fins: number; } function isBird(animal: Bird | Fish): animal is Bird { return (animal as Bird).fly !== undefined; } } const pet: Bird | Fish = ...; if (isBird(pet)) { pet.fly(); // OK: pet은 이제 Bird } else { pet.swim(); // OK: pet은 이제 Fish }
주요 특징:
명시적으로 'param is Type'을 반환 타입 시그니처에 지정하지 않으면 타입 가드 함수가 작동할 수 있습니까?
아니요, 반환 타입에 'param is Type'을 명시적으로 지정하지 않으면, TypeScript는 코드 분기에서 타입을 축소할 수 없습니다. 컴파일러는 매개변수를 특정 타입으로 사용할 수 있다는 것을 이해하지 못합니다.
코드 예시:
function isFish(animal: Fish | Bird): boolean { return (animal as Fish).swim !== undefined; } // 작동합니까? if (isFish(pet)) { pet.swim(); // 오류: 'swim' 속성이 존재하지 않습니다 }
문자열이나 숫자와 같은 원시 값을 확인하는 데 타입 프레디케이트를 사용할 수 있습니까?
예, 사용할 수 있지만 typeof가 더 많이 사용되며 이러한 가드는 과잉일 수 있습니다. 그럼에도 불구하고 사용자 정의 가드를 구현하는 것은 가능합니다:
function isString(x: unknown): x is string { return typeof x === "string"; }
타입 가드 함수가 컴파일 단계에서 타입 오류에 대해 엄격하게 보호합니까?
완벽하게는 아닙니다. TypeScript는 함수 구현에 의존하며 내부 로직의 정확성을 확인할 수 없습니다. 검사를 잘못 구현하면 컴파일러는 오류를 이해하지 못하며, 실행 시 문제가 발생합니다.
function isFish(animal: Fish | Bird): animal is Fish { // 잘못됨: 항상 true를 반환합니다 return true; }
부정적인 케이스 개발자가 프레디케이트 함수를 구현했지만 구조 검사를 잘못하여 함수가 항상 true를 반환했습니다. 코드는 컴파일을 통과했지만 런타임 단계에서 존재하지 않는 메서드가 호출되었습니다.
장점:
단점:
긍정적인 케이스 타입 프레디케이트 함수가 올바르게 구현되고 극한 값 및 오류 데이터에 대한 단위 테스트를 통과했습니다.
장점:
단점: