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

如何在TypeScript中正确使用Set<T>处理交叉类型?

TypeScript 序列化器集合报错原因及最优解决方案

报错原因解释

TypeScript 抛出这个错误本质是因为函数参数的逆变特性:

  • 你的 Serializer<E> 是一个接受 E 类型参数的函数,函数参数的类型遵循逆变规则——如果 A extends B,那么接受 B 的函数可以赋值给接受 A 的函数,但反过来不行。
  • Serializer<SvgElement> 代表一个能处理任意SvgElement(包括圆形、路径等所有子类型)的函数;而 pathSerializer 是 Serializer<SvgPathElement>,它只能处理路径类型的元素。如果允许把它加入 Set<Serializer<SvgElement>>,后续代码可能会传入圆形元素给它,这显然会触发类型错误,所以 TypeScript 直接阻止了这个不安全的赋值。

最优解决方案(无需使用 any)

下面提供两种兼顾类型安全和扩展性的方案:

方案一:使用联合类型约束集合类型

直接把集合的类型定义为所有具体子类型序列化器的联合类型,完全保留类型校验:

export enum SvgElementType {
  path = "path",
  circle = "circle",
}

export type SvgElement = {
  id: number;
  type: SvgElementType;
};

export type SvgPathElement = SvgElement & {
  type: SvgElementType.path;
  d: string;
};

export type SvgCircleElement = SvgElement & {
  type: SvgElementType.circle;
  cx: number;
  cy: number;
  radius: number;
};

export type Serializer<E extends SvgElement = SvgElement> = (element: E) => string;

const pathSerializer: Serializer<SvgPathElement> = e => `<path d="${e.d}" />`;
const circleSerializer: Serializer<SvgCircleElement> = e => `<circle cx="${e.cx}" cy="${e.cy}" r="${e.radius}" />`;

// 改为联合类型的集合
const serializers: Set<Serializer<SvgPathElement> | Serializer<SvgCircleElement>> = new Set();
serializers.add(pathSerializer); // 正常通过
serializers.add(circleSerializer); // 正常通过

优点:完全类型安全,调用时 TypeScript 能自动识别每个序列化器的参数类型;缺点:新增SvgElement子类型时,需要手动更新集合的联合类型。

方案二:带类型标识的序列化器(推荐,扩展性强)

给序列化器添加一个type字段标识它能处理的元素类型,配合类型守卫在调用时自动匹配,既保留类型安全又无需频繁修改集合类型:

export enum SvgElementType {
  path = "path",
  circle = "circle",
}

export type SvgElement = {
  id: number;
  type: SvgElementType;
};

export type SvgPathElement = SvgElement & {
  type: SvgElementType.path;
  d: string;
};

export type SvgCircleElement = SvgElement & {
  type: SvgElementType.circle;
  cx: number;
  cy: number;
  radius: number;
};

// 扩展Serializer类型,增加type字段用于匹配元素类型
export type Serializer<E extends SvgElement = SvgElement> = {
  handlesType: E["type"];
  serialize: (element: E) => string;
};

// 改写序列化器实例
const pathSerializer: Serializer<SvgPathElement> = {
  handlesType: SvgElementType.path,
  serialize: e => `<path d="${e.d}" />`
};

const circleSerializer: Serializer<SvgCircleElement> = {
  handlesType: SvgElementType.circle,
  serialize: e => `<circle cx="${e.cx}" cy="${e.cy}" r="${e.radius}" />`
};

// 集合类型直接用Serializer即可
const serializers: Set<Serializer> = new Set();
serializers.add(pathSerializer); // 正常通过
serializers.add(circleSerializer); // 正常通过

// 调用时通过类型守卫自动匹配正确的序列化器
function serializeElement(element: SvgElement): string {
  for (const serializer of serializers) {
    if (serializer.handlesType === element.type) {
      // TypeScript会自动推断当前serializer能处理element的类型
      return (serializer as Serializer<typeof element>).serialize(element);
    }
  }
  throw new Error(`No serializer found for element type: ${element.type}`);
}

优点:扩展性强,新增子类型时只需添加对应序列化器,无需修改集合类型;调用时完全保留类型校验,不会出现类型错误。


内容的提问来源于stack exchange,提问作者A. Masson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 04:37:10