You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 01:45:07