프로그래밍프론트엔드 개발자

타입스크립트는 사용자 정의 타입 가드(user-defined type guards)와 어떻게 작동하며, 그들이 필요한 이유와 올바른 타입 프레디케이트(type predicate) 함수를 만드는 방법은 무엇인가요? 구현할 때 흔히 발생하는 함정은 무엇인가요?

Hintsage AI 어시스턴트로 면접 통과

답변.

문제의 역사: 타입스크립트는 일반적인 타입 체크를 확장하여 개발자가 객체가 특정 타입에 부합하는지를 검사하는 사용자 정의 함수인 타입 가드를 만들 수 있게 합니다. 이는 유니언 타입, 동적 구조 및 값의 타입이 다를 수 있는 API와 작업하는 데 필요합니다.

문제는 종종 typeof 및 instanceof를 통한 일반적인 타입 체크가 원시 타입과 클래스에 제한되며, 하지만 구조체나 복잡한 타입은 그렇게 정의할 수 없다는 점입니다. 컴파일러에게 값이 안전하게 원하는 타입으로 좁혀지는 경우를 명시적으로 알려주는 방법이 필요합니다.

해결책은 다음과 같은 형태의 타입 가드 함수를 작성하는 것입니다: function isCat(obj: Animal): obj is Cat {...}, 여기서 중요한 점은 함수의 반환 타입에 타입 프레디케이트가 있다는 것입니다.

코드 예시:

interface Dog { bark: () => void; } interface Cat { meow: () => void; } type Pet = Dog | Cat; function isDog(pet: Pet): pet is Dog { return (pet as Dog).bark !== undefined; } function makeSound(pet: Pet) { if (isDog(pet)) { pet.bark(); } else { pet.meow(); } }

주요 특징:

  • 타입 체크는 param is Type 형태의 특별한 타입 프레디케이트 함수를 통해 실현됩니다.
  • 자신만의 가드를 만들고 구조체(클래스 객체뿐만 아니라)를 위해 사용할 수 있습니다.
  • 잘못된 타입 가드는 런타임 오류 및 잘못된 타입 축소를 초래할 수 있습니다.

트릭 질문들.

타입 가드 함수에서 true/false를 반환하기만 하면 컴파일러가 타입을 좁혀주나요?

아니요. 반환 타입을 타입 프레디케이트(예: pet is Dog)로 명시적으로 지정하는 것이 중요합니다. 그렇지 않으면 타입스크립트는 값의 타입을 자동으로 좁히지 않습니다. 함수가 단지 true 또는 false를 반환하더라도요.

타입 가드를 콜백 내에서 사용할 수 있나요 (예: filter에서 사용)? 그리고 축소가 올바르게 작동하나요?

네, 타입 가드가 올바르게 주석 처리되어 있다면, 컴파일러는 filter 후 배열 요소의 타입을 좁히고 forEach/callback 함수 내에서도 마찬가지입니다. 하지만 주석이 누락되거나 잘못 작성되면 결과는 특정 타입 대신 유니언 타입을 갖게 됩니다.

const pets: Pet[] = [...]; const dogs = pets.filter(isDog); // 타입스크립트는 dogs: Dog[]임을 알 수 있습니다.

사용자 정의 타입 가드가 typeof, instanceof를 통한 일반적인 타입 체크와 어떤 점에서 다르나요?

타입 가드 함수는 모든 구조체에 대한 검사를 구현하고, 복잡한 수준의 검사를 설명하고, 클래스 없이 인터페이스를 조작할 수 있으며, 기본 타입과 클래스만을 사용하는 것이 아닙니다.

전형적인 오류 및 안티 패턴

  • 타입 가드가 type-predicate 타입을 반환하지 않고 단순한 boolean을 반환하여 컴파일러에 대한 효과가 없음.
  • 표면적인 키만을 검사하고 구조적 무결성을 보장하지 않음.
  • 타입 가드가 항상 true를 반환하여 사실상 타입 보호를 비활성화함.
  • 입력 파라미터가 너무 일반적이거나 검증 없이 암시적 타입 변환이 이루어짐.

실제 사례

부정적인 케이스

함수가 타입 프레디케이트 없이 타입 가드를 사용하여 사용자를 필터링합니다:

function isValidAdmin(user: any): boolean { return user.isAdmin === true; } const admins = users.filter(isValidAdmin); // admins: any[]

장점:

  • 빠르며, 런타임에서 효과가 뚜렷함

단점:

  • 필터링 후 타입이 타입스크립트에 의해 명확해지지 않아 속성 접근 시 오류 가능성 있음

긍정적인 케이스

함수가 올바른 타입 프레디케이트로 필터링을 수행합니다:

interface Admin { name: string; isAdmin: true; } function isAdmin(user: any): user is Admin { return user && user.isAdmin === true; } const admins = users.filter(isAdmin); // admins: Admin[]

장점:

  • 결과의 타입이 정확하게 알려짐
  • 결과 작업 시 오류 가능성이 줄어듬

단점:

  • 시그니처와 체크 커버리지에 대해 더 많은 주의가 필요함