React TypeScript中如何建模含二选一字段的状态?
问题
我需要定义一种Item类型,它要么持有自身的数值,要么通过ID引用其他项,具体定义如下:
interface Item { name: string; value: { valueLiteral: number } | { id: string }; }
但在React组件中根据value的类型分支渲染时,TypeScript会报错:
Property 'id' does not exist on type '{ valueLiteral: number; } | { id: string; }'.
Property 'id' does not exist on type '{ valueLiteral: number; }'.ts(2339)
如果把value和id拆成可选字段,虽然能避免错误,但会丢失类型安全性——TypeScript无法阻止同时存在/缺失两个字段的无效情况,不符合我的建模需求。
解决方案
要在保留类型安全性的前提下解决这个问题,核心是给TypeScript提供类型守卫,让它能正确识别联合类型的具体分支。
方法1:直接使用in操作符做类型窄化
不需要额外定义函数,直接在条件判断中用in操作符,TypeScript会自动完成类型窄化:
interface Item { name: string; value: { valueLiteral: number } | { id: string }; } const item1: Item = { name: "foo1", value: { valueLiteral: 1 } }; const item2: Item = { name: "foo2", value: { id: "some-id" } }; function ItemComponent({ item }: { item: Item }) { if ('id' in item.value) { return <div>{item.name} - ID = {item.value.id}</div>; } // 此时TypeScript自动推断剩余类型为{ valueLiteral: number } return <div>{item.name} - VALUE = {item.value.valueLiteral}</div>; }
方法2:自定义类型守卫函数
如果需要在多个地方复用类型判断逻辑,可以定义一个类型守卫函数:
interface Item { name: string; value: { valueLiteral: number } | { id: string }; } // 自定义类型守卫,判断value是否为带id的类型 function isIdValue(value: Item['value']): value is { id: string } { return 'id' in value; } function ItemComponent({ item }: { item: Item }) { if (isIdValue(item.value)) { return <div>{item.name} - ID = {item.value.id}</div>; } return <div>{item.name} - VALUE = {item.value.valueLiteral}</div>; }
方法3:使用可辨识联合类型(推荐)
如果可以调整数据结构,推荐给联合类型的子类型添加一个共同的区分字段(如type),这是TypeScript官方推荐的可辨识联合类型写法,语义更清晰,类型推断更准确:
// 定义可辨识联合类型,通过type字段区分分支 type Item = | { name: string; type: 'literal'; valueLiteral: number } | { name: string; type: 'reference'; id: string }; const item1: Item = { name: "foo1", type: 'literal', valueLiteral: 1 }; const item2: Item = { name: "foo2", type: 'reference', id: "some-id" }; function ItemComponent({ item }: { item: Item }) { if (item.type === 'reference') { return <div>{item.name} - ID = {item.id}</div>; } return <div>{item.name} - VALUE = {item.valueLiteral}</div>; }
这种写法从根源上确保了Item只能是两种互斥类型之一,完全符合你的数据建模需求,同时彻底避免了类型错误。
内容的提问来源于stack exchange,提问作者Evanss
相关产品推荐
相关产品推荐

