TypeScript联合类型问题:onChange类型推断异常求助
TypeScript联合类型窄化异常:else分支onChange类型推断为交集而非预期类型
问题场景
定义了带交叉属性的联合类型SelectAnswersProps,通过isMultiSelect分支判断调用onChange时,if分支类型推断正确,但else分支的onChange被推断为两种SetStateAction类型的交集,而非对应isMultiSelect: false的单一类型。
原类型定义:
type SelectAnswersProps = ( | { onChange: Dispatch<SetStateAction<MultiSelectAnswerOptionsType | null>>; value: MultiSelectAnswerOptionsType; isMultiSelect: true; } | { onChange: Dispatch<SetStateAction<SelectAnswerOptionsType | null>>; value: SelectAnswerOptionsType; isMultiSelect: false; } ) & { displayMode?: SelectAnswerOptionsType['displayMode']; disabled?: boolean; };
调用代码:
if (isMultiSelect) { onChange({ answers, isHeadless, }); } else { onChange({ answers, displayMode, }); }
原因分析
当联合类型与一个独立对象类型交叉(& { displayMode?: ...; disabled?: ... })时,TypeScript的类型窄化逻辑会出现偏差。原本的两个联合分支被交叉后,相当于每个分支都附加了额外属性,但在else分支中,TypeScript无法正确将isMultiSelect: false与对应的onChange类型关联,反而将两个分支的onChange类型取了交集,导致推断异常。
解决方案
方案1:将交叉属性合并到每个联合分支中
把displayMode和disabled直接放到两个联合分支里,避免使用&交叉整个联合类型:
type SelectAnswersProps = | { onChange: Dispatch<SetStateAction<MultiSelectAnswerOptionsType | null>>; value: MultiSelectAnswerOptionsType; isMultiSelect: true; displayMode?: SelectAnswerOptionsType['displayMode']; disabled?: boolean; } | { onChange: Dispatch<SetStateAction<SelectAnswerOptionsType | null>>; value: SelectAnswerOptionsType; isMultiSelect: false; displayMode?: SelectAnswerOptionsType['displayMode']; disabled?: boolean; };
这种方式让每个联合分支的类型更明确,TypeScript能正确根据isMultiSelect的值窄化对应的onChange类型。
方案2:使用自定义类型守卫强制窄化
如果不想重复代码,可以定义一个自定义类型守卫来明确当前props的类型:
function isMultiSelectProps( props: SelectAnswersProps ): props is Extract<SelectAnswersProps, { isMultiSelect: true }> { return props.isMultiSelect; } // 调用时使用类型守卫 if (isMultiSelectProps(props)) { props.onChange({ answers, isHeadless }); } else { props.onChange({ answers, displayMode }); }
通过Extract工具类型提取对应分支,配合自定义守卫,TypeScript能准确推断else分支的类型。
方案3:使用类型断言(不推荐,仅应急)
如果上述方案都不适用,可以临时用类型断言强制指定else分支的onChange类型,但这会丢失类型检查的安全性:
else { (onChange as Dispatch<SetStateAction<SelectAnswerOptionsType | null>>)({ answers, displayMode, }); }
内容的提问来源于stack exchange,提问作者Ptak
相关产品推荐
相关产品推荐

