正确使用 Typescript Set<T> 和交叉类型

问题描述 投票:0回答:1

我不明白为什么编译器会抱怨。以下是基本类型声明:

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 => "";
const circleSerializer: Serializer<SvgCircleElement> = e => "";

const serializers: Set<Serializer> = new Set();
serializers.add(pathSerializer); // <<<--- transpile error

// full TS error
// Argument of type 'Serializer<SvgPathElement>' is not assignable to parameter of type 'Serializer<SvgElement>'.
//  Type 'SvgElement' is not assignable to type 'SvgPathElement'.
//    Property 'd' is missing in type 'SvgElement' but required in type '{ type: SvgElementType.path; d: string; }'.ts(2345)

我发现的唯一方法是修改

Serializer
的声明,将
any
作为默认类型:

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

这标志着我可能有更好的方法来保留稍后设置迭代器使用的最少类型...

    

typescript generics set intersection-types
1个回答
0
投票

serializers

这将编译,因为集合 
function serialize(e: SvgElement) { for (const s of serializers) { return s(element); } }

的每个成员都被声明为接受任何类型的

serializers
- 但这实际上并非如此,因为每个序列化器仅接受
特定
类型的元素,并且会因类型而失败如果使用不同类型调用,则会在运行时出错。 那么如何写出正确的类型呢?

在回答这个问题之前,让我们想象一下序列化器将如何使用,因为我有点惊讶

SvgElement

是一个集合。我们如何在这个

serializers
中找到正确的
Serializer
?在我看来,
Set
对于这个目的来说是一个非常糟糕的数据结构,而
Set
或字典对象将是一个更直接的选择。
因此,我会使用这样的东西:

Map

通过使用映射类型声明 
export type SvgElementBase<T extends string> = { id: number; type: T; } export type SvgPathElement = SvgElementBase<"path"> & { d: string; }; export type SvgCircleElement = SvgElementBase<"circle"> & { cx: number; cy: number; radius: number; }; export type SvgElement = SvgPathElement | SvgCircleElement; export type SvgElementType = SvgElement["type"]; export type Serializer<E extends SvgElement> = (e: E) => string; export type Serializers = {[T in SvgElementType]: Serializer<SvgElement & SvgElementBase<T>>} const serializers: Serializers = { path: (e) => `path ${e.d}`, circle: (e) => `circle at (${e.cx},${e.cy}) with radius ${e.radius}`, } function lookupSerializer<E extends SvgElement>(type: E["type"]): Serializer<E> { // the things we do for type safety return serializers[type]; } export function serialize(element: SvgElement) { return lookupSerializer(element.type)(element); }

,我们可以表示键和值的类型是相关的,即为特定类型注册的序列化器仅接受该特定类型的值。通用

serializers
函数促使编译器实际分发联合,如果要内联它,编译器将不会分发联合,因此尽管实现很简单,但该函数实际上是必要的。
    

© www.soinside.com 2019 - 2024. All rights reserved.