React TypeScript联合类型收窄疑问:toggler函数类型无法正确识别?
React TypeScript联合类型Props收窄问题解答
核心结论
TypeScript可以通过单个属性收窄整个联合类型,但你的代码存在两个导致收窄失败的问题,下面逐一拆解并给出修复方案。
问题原因分析
解构破坏了类型关联
你提前解构了props的labels、toggler、values,此时TypeScript无法将这三个变量的类型绑定到原联合类型的子类型上。解构后的toggler会变成((from: number, to: number) => void) | ((value: string) => void)的联合类型,后续判断Array.isArray(labels)时,TypeScript没法把这个判断和已经独立出来的toggler关联,自然无法收窄其类型。未基于原对象做类型判断
单独对解构后的labels做类型检查,TypeScript无法回溯到原props的联合类型,也就没法联动更新其他属性的类型推断。
正确的收窄方式
方案1:直接对原props做类型判断(推荐)
不要提前解构,直接判断props.labels的类型,此时TypeScript会自动收窄整个props的类型,连带toggler等属性的类型也会被正确识别:
import { Chip } from "@mui/material"; type CourseFilterChipsRangeType = { labels: { from: string; to: string }; values: { from: number; to: number }; toggler: (from: number, to: number) => void; }; type CourseFilterChipsCheckType = { labels: string[]; toggler: (value: string) => void; values?: never; }; type CourseFilterChipsProps = CourseFilterChipsRangeType | CourseFilterChipsCheckType; const CourseFilterChips = (props: CourseFilterChipsProps) => { let chips: JSX.Element[] = []; // 直接判断props.labels的类型,TypeScript自动收窄整个props的类型 if (Array.isArray(props.labels)) { chips = props.labels.map((label) => ( <Chip onDelete={() => props.toggler(label.toLowerCase())} label={label} /> )); } else { // 处理范围类型的Chip逻辑 chips = [ <Chip onDelete={() => props.toggler(props.values.from, props.values.to)} label={`${props.labels.from} - ${props.labels.to}`} /> ]; } return <>{chips}</>; }; export default CourseFilterChips;
方案2:自定义类型守卫函数
如果需要复用类型判断逻辑,可以自定义类型守卫,明确告知TypeScript当前props属于哪个子类型:
import { Chip } from "@mui/material"; type CourseFilterChipsRangeType = { labels: { from: string; to: string }; values: { from: number; to: number }; toggler: (from: number, to: number) => void; }; type CourseFilterChipsCheckType = { labels: string[]; toggler: (value: string) => void; values?: never; }; type CourseFilterChipsProps = CourseFilterChipsRangeType | CourseFilterChipsCheckType; // 自定义类型守卫:判断是否为多选类型 function isCheckType(props: CourseFilterChipsProps): props is CourseFilterChipsCheckType { return Array.isArray(props.labels); } // 自定义类型守卫:判断是否为范围类型 function isRangeType(props: CourseFilterChipsProps): props is CourseFilterChipsRangeType { return !Array.isArray(props.labels) && 'from' in props.labels && 'to' in props.labels; } const CourseFilterChips = (props: CourseFilterChipsProps) => { let chips: JSX.Element[] = []; if (isCheckType(props)) { chips = props.labels.map((label) => ( <Chip onDelete={() => props.toggler(label.toLowerCase())} label={label} /> )); } else if (isRangeType(props)) { chips = [ <Chip onDelete={() => props.toggler(props.values.from, props.values.to)} label={`${props.labels.from} - ${props.labels.to}`} /> ]; } return <>{chips}</>; }; export default CourseFilterChips;
关键知识点总结
- 联合类型收窄的核心是让TypeScript能将多个属性的类型关联到原联合类型的子类型,提前解构会打破这种关联。
- 直接对原对象做类型判断是最简单有效的收窄方式,TypeScript会自动联动更新对象其他属性的类型。
- 复杂场景下可使用自定义类型守卫,明确告知TypeScript当前对象的具体子类型。
内容的提问来源于stack exchange,提问作者Abolhasan Ashori
相关产品推荐
相关产品推荐

