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는 타입을 필요한 옵션으로 "좁혀" 줍니다.
주요 장점:
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가 올바르게 작동하지 않았고, 새로운 옵션들이 기존 로직을 "깨뜨렸습니다."