如何在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
相关产品推荐
相关产品推荐

