TypeScript特定类型处理函数传入联合类型入参函数报错修复
复现代码如下:
interface DataOne { strings: string[], } interface DataTwo { numbers: number[], } const Component = (onDataReceived: (data: DataOne | DataTwo) => void) => { const data: DataOne | DataTwo = {numbers: [1, 2, 3]} onDataReceived(data) } const onDataOneReceived = (data: DataOne) => console.log(data) Component(onDataOneReceived)
编译时报错:
Argument of type '(data: DataOne) => void' is not assignable to parameter of type '(data: DataOne | DataTwo) => void'. Types of parameters 'data' and 'data' are incompatible.
Type 'DataOne | DataTwo' is not assignable to type 'DataOne'.
Property 'strings' is missing in type 'DataTwo' but required in type 'DataOne'.
这个错误是TypeScript函数参数逆变校验的正常结果:Component声明的入参回调,必须能覆盖DataOne | DataTwo联合类型的所有取值场景,也就是回调要同时兼容两种数据类型。但你传入的onDataOneReceived只声明接收DataOne类型,一旦Component内部传入DataTwo类型数据,回调里访问strings属性就会出现运行时错误,因此TS直接拦截了这个不合法的类型传参。
之前尝试泛型、类型守卫没生效,基本是用法不对,下面两种方案都可以解决问题:
方案1:回调内部做类型窄化(无需修改Component定义,适配所有场景)
把回调的参数类型声明为完整的联合类型,在回调内部通过类型守卫判断数据的实际类型,再执行对应逻辑,TS会自动完成类型窄化:
const onDataOneReceived = (data: DataOne | DataTwo) => { // 过滤非DataOne类型的场景 if (!('strings' in data)) return // 此处TS会自动推导data为DataOne类型,可安全访问strings属性 console.log(data.strings) } // 调用无类型报错 Component(onDataOneReceived)
如果需要同时处理两种数据类型,直接在判断分支里写对应逻辑即可:
const onDataReceived = (data: DataOne | DataTwo) => { if ('strings' in data) { console.log('字符串数组数据:', data.strings) } else { console.log('数字数组数据:', data.numbers) } } Component(onDataReceived)
方案2:为Component添加正确的泛型约束(适合Component返回数据类型可推导的场景)
如果Component的返回数据类型可以通过入参确定、不是内部随机生成的固定联合类型,可以用泛型关联入参回调和数据的类型,避免手动窄化:
// 约束泛型T为两个接口的联合类型 const Component = <T extends DataOne | DataTwo>( onDataReceived: (data: T) => void, sourceData: T ) => { onDataReceived(sourceData) } // 传入DataTwo类型数据时,TS自动推导回调参数为DataTwo,可直接访问numbers Component((data) => { console.log(data.numbers) }, { numbers: [1,2,3] }) // 传入DataOne类型数据时,TS自动推导回调参数为DataOne,可直接访问strings Component((data) => { console.log(data.strings) }, { strings: ['a', 'b'] })
注意:如果Component内部生成数据、类型不受外部入参控制,不要用泛型方案,必须在回调内部做类型窄化,否则会绕过TS的类型安全校验,埋下运行时错误隐患。
内容的提问来源于stack exchange,提问作者barciewicz

