리터럴 타입 공통 속성(variant)으로 union 을 가르면 분기 안에서 좁혀진다.1 no-title 에 title 을 붙이면 막히는 것도 같은 축이다 — 초과 속성 검사가 variant 로 고른 멤버 하나에 대고 이뤄진다.2
import * as React from 'react'
import { IsEqual } from 'type-plus'
type ModalProps =
| {
variant: 'no-title'
}
| {
variant: 'title'
title: string
}
export const Modal = (props: ModalProps) => {
if (props.variant === 'no-title') {
return <div>No title</div>
} else {
type Test = IsEqual<
typeof props,
{
variant: 'title'
title: string
}
>
return <div>Title: {props.title}</div>
}
}
export const Test = () => {
return (
<div>
<Modal variant="title" title="Hello" />
<Modal variant="no-title" />
{/* @ts-expect-error */}
<Modal />
<Modal
variant="no-title"
// @ts-expect-error
title="Oh dear"
/>
</div>
)
}
Footnotes
-
TypeScript Handbook — Narrowing, Discriminated unions: “When every type in a union contains a common property with literal types, TypeScript considers that to be a discriminated union, and can narrow out the members of the union.” ↩
-
판별 속성이 없는 union(
{ a: number } | { b: string })은{ a: 1, b: 'x' }를 받는다 — 3.5 릴리스 노트도 “partial overlap is still permitted” 라 한다. 막히는 건 검사 대상을 판별 속성으로 먼저 좁히기 때문이다: TypeScript v5.9.3checker.ts—hasExcessPropertiesreducedTarget = findMatchingDiscriminantType(source, target, …). ↩