프로그래밍TypeScript 아키텍트

TypeScript에서 discriminated unions를 통해 패턴 매칭은 어떻게 구현되나요? 타입을 어떻게 올바르게 구조화하고 어떤 함정이 있을까요?

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

답변

TypeScript에서 패턴 매칭은 "discriminated unions"(차별화된 유니온)를 통해 구현됩니다. 유니온의 각 객체에는 필수적인 구분 필드(일반적으로 문자열, 예: type)가 할당되어 있으며, TypeScript는 이 필드에 따라 옵션을 구분합니다.

예시:

type Success = { type: 'success'; data: string }; type Failure = { type: 'failure'; error: string }; type Result = Success | Failure; function handleResult(result: Result) { switch (result.type) { case 'success': // result: Success console.log(result.data); break; case 'failure': // result: Failure console.error(result.error); break; } }

스위치/케이스 또는 if 문에서 구분 필드에 따라 TypeScript는 타입을 필요한 옵션으로 "좁혀" 줍니다.

주요 장점:

  • 엄격한 타입 검사 — 존재하지 않는 필드에 접근할 수 없습니다.
  • exhaustive 검사 — 모든 옵션을 처리하지 않으면 간혹 오류가 발생합니다(명시적으로 강제할 수 있음).

함정이 있는 질문

discriminated union에 새로운 옵션을 추가하면 TypeScript가 강제로 모든 switch-case를 업데이트하여 새 옵션을 처리하도록 요구하나요?

답변: 아니요, "불가능한" 옵션에 대한 처리를 명시적으로 추가하지 않는 한 요구하지 않습니다. 예를 들어 never 함수를 사용할 수 있습니다:

예시:

function assertNever(x: never): never { throw new Error('Unexpected variant: ' + x); } function handle(r: Result) { switch(r.type) { case 'success': /* ... */; break; case 'failure': /* ... */; break; default: return assertNever(r); // TS는 새로운 타입이 추가되면 오류를 발생시킵니다. } }

주제에 대한 미세한 지식 부족으로 인한 실제 오류 사례.


스토리

"Result" 타입에 새로운 옵션('pending')을 추가한 후 애플리케이션의 여러 위치에서 구형 switch-case가 이 케이스를 처리하지 않아 인터페이스의 일부가 작동하지 않게 되었습니다. 오류는 배포 후 일주일이 지나서야 감지되었습니다.


스토리

두 타입에 type 필드가 중복되어 고유한 구분자가 없는 discriminated union을 사용하려고 하자 타입의 "희석"이 발생했습니다. TypeScript는 타입을 정확하게 좁히지 못하게 되었고 컴파일 오류 없이 존재하지 않는 필드에 접근할 수 있게 되었습니다. 몇 가지 심각한 버그가 프로덕션에 반영되었습니다.


스토리

프로젝트에서 패턴 매칭을 명시적인 구분자 하나 대신 여러 필드로 if-else를 통해 구현하였습니다. 이로 인해 never 함수를 사용할 수 없는 exhaustive 검사로의 전환이 복잡해졌고, 코드 가독성이 떨어졌습니다 — switch-case가 올바르게 작동하지 않았고, 새로운 옵션들이 기존 로직을 "깨뜨렸습니다."