TypeScript中为可辨识联合编写监听器函数的类型问题
TypeScript可辨识联合类型的监听器类型兼容问题解决方法
先看定义的可辨识联合类型:
type Circle = { type: 'circle'; radius: number }; type Square = { type: 'square'; x: number }; type Rectangle = { type: 'rectangle'; x: number; y: number }; type Shape = Circle | Square | Rectangle;
需求是实现一个监听器系统,调用addListener传入形状类型和对应回调,检测到对应形状时触发回调。但原代码触发类型错误:
type Listener<Type extends Shape> = { type: Type['type']; callback: (detected: Type) => void; }; const listeners: Listener<Shape>[] = []; function addListener<Type extends Shape>(type: Type['type'], callback: (detected: Type) => void) { listeners.push({ type, callback }); // Error: Type '(detected: Type) => void' is not assignable to type '(detected: Shape) => void'. }
报错核心原因是TypeScript的函数参数逆变特性:(detected: Type) => void不能直接赋值给(detected: Shape) => void——Shape是更大的类型集合,回调可能收到不兼容的形状。下面是几种解决方法,同时保留回调参数的类型校验:
方法一:将Listener定义为联合类型
直接生成每个形状对应Listener类型的联合,而非依赖泛型:
type Listener = { [T in Shape as T['type']]: { type: T['type']; callback: (detected: T) => void; } }[Shape['type']]; const listeners: Listener[] = []; function addListener<T extends Shape>(type: T['type'], callback: (detected: T) => void) { listeners.push({ type, callback } as Listener); }
这里用映射类型遍历Shape的每个成员,生成对应type和回调的结构,再取出所有type对应的类型组成联合。addListener的泛型约束保证了type和回调参数类型匹配,断言为Listener是安全的。
方法二:安全类型断言
直接对监听器对象做类型断言,同时保留addListener的泛型校验:
type Listener<Type extends Shape> = { type: Type['type']; callback: (detected: Type) => void; }; const listeners: Listener<Shape>[] = []; function addListener<Type extends Shape>(type: Type['type'], callback: (detected: Type) => void) { listeners.push({ type, callback } as Listener<Shape>); }
这种方式最简单,因为我们明确知道触发回调时只会传入对应type的形状,所以断言是安全的,同时用户调用addListener时,TypeScript依然会校验type和回调参数是否匹配。
方法三:包装回调做类型守卫
通过包装回调,内部用类型守卫确保只处理对应形状,无需额外断言:
type Listener = { type: Shape['type']; callback: (detected: Shape) => void; }; const listeners: Listener[] = []; function addListener<T extends Shape>(type: T['type'], callback: (detected: T) => void) { listeners.push({ type, callback: (detected: Shape) => { if (detected.type === type) { callback(detected); // TypeScript会自动推断detected是T类型 } } }); }
这种方式最安全,回调内部通过detected.type === type做类型守卫,TypeScript会自动缩小detected的类型到T,直接调用原回调即可,完全避免了类型断言。
内容的提问来源于stack exchange,提问作者user2248702
相关产品推荐
相关产品推荐

