TypeScript回调参数条件类型报错:Callback类型交集问题排查
你遇到的这个类型错误,核心原因是TypeScript在泛型函数中无法自动将T和storage[type]的具体类型做精确关联,导致它把storage[type]推断成了Set<Callback<"ok">> | Set<Callback<"error">>,而联合类型的方法参数遵循逆变规则,最终add的参数类型变成了Callback<"ok"> & Callback<"error">——这就是你看到交集类型的由来。
为什么会这样?
当你访问storage[type]时,TypeScript只知道type是Mapping['type'](也就是"ok" | "error"),但它没办法确认type的具体值和callback的类型是严格对应的。所以它只能把storage[type]当成两个Set的联合类型,而联合类型的函数方法参数会取交集(逆变特性)——因为只有同时满足两个Set的add参数要求的值,才能安全地被添加到任意一个Set中。但你的callback是针对特定T的,自然不满足这个交集类型。
解决方案
我们可以通过类型断言或者显式类型窄化来告诉TypeScript,storage[type]的类型和Callback<T>是匹配的,这里有几种可行的方式:
1. 直接类型断言(最简单的方式)
修改store函数,把storage[type]断言为Set<Callback<T>>,明确告诉TypeScript两者类型匹配:
function store<T extends Mapping['type']>(type: T, callback: Callback<T>): void { (storage[type] as Set<Callback<T>>).add(callback); }
2. 用switch case做显式类型窄化
如果不想用断言,可以通过switch语句将type的具体值和对应的Set关联起来,让TypeScript自动推断正确的类型:
function store<T extends Mapping['type']>(type: T, callback: Callback<T>): void { switch (type) { case 'ok': storage.ok.add(callback as Callback<"ok">); break; case 'error': storage.error.add(callback as Callback<"error">); break; } }
这里的小断言是因为TypeScript暂时无法把泛型T和具体的分支类型完全绑定,但逻辑上是安全的。
3. 重构类型,让关联更明确
你也可以调整Store和store函数的定义,让TypeScript能自动推断类型关联,比如用一个辅助函数来创建存储:
// 辅助函数,确保类型关联 function getStorage<T extends Mapping['type']>(type: T): Set<Callback<T>> { return storage[type] as Set<Callback<T>>; } function store<T extends Mapping['type']>(type: T, callback: Callback<T>): void { getStorage(type).add(callback); }
这种方式把断言封装在辅助函数里,让主函数更干净。
验证效果
修改后,当你调用store("ok", (data) => { /* data是Record<string, string> */ })或者store("error", (err) => { /* err是Error */ })时,TypeScript会正确推断参数类型,同时不会再出现类型错误。
内容的提问来源于stack exchange,提问作者Zauberbutter

