在具有特定类型的可区分联合中获取 class 的实例,而不是联合类型
Get instance of a class in a discriminated union with its specific type, rather than the union type
有没有一个很好的方法可以让一个函数根据你传递给它的共享 属性 的值创建一个 class 的新实例,并让它 return 创建实例的具体类型,而不是联合类型?使用形状稍微调整打字稿文档示例:
class Square {
kind: "square";
size: number;
}
class Rectangle {
kind: "rectangle";
width: number;
height: number;
}
class Circle {
kind: "circle";
radius: number;
}
type Kinds = "circle" | "rectangle" | "square";
type Shape = Square | Rectangle | Circle;
function createShape(kind: Kinds) {
switch (kind) {
case "circle":
return new Circle();
case "rectangle":
return new Rectangle();
case "square":
return new Square();
}
}
createShape("circle").radius; //Property 'radius' does not exist on type 'Square | Rectangle | Circle'
例如,我可以在 Kinds
和 Shape
之间添加一个映射,并向函数添加一些类型注释:
type Kinds = "circle" | "rectangle" | "square";
type Shape = Square | Rectangle | Circle;
type ShapeKind = { "circle": Circle, "square": Square, "rectangle": Rectangle };
function createShape<T extends Kinds>(kind: T): ShapeKind[T] {
switch (kind) {
case "circle":
return new Circle();
case "rectangle":
return new Rectangle();
case "square":
return new Square();
}
}
createShape("circle").radius; //All good now
但是必须创建此映射感觉有点讨厌。我也可以使用类型保护,但这感觉很多余,因为我在创建和 return 时确定知道 Shape 的类型。有没有更好的方法来处理这个问题?
您不必创建映射;你可以从你的Shape
类型中提取它:
class Square {
readonly kind = "square";
size!: number;
}
class Rectangle {
readonly kind = "rectangle";
width!: number;
height!: number;
}
class Circle {
readonly kind = "circle";
radius!: number;
}
type Shape = Square | Rectangle | Circle;
type Kinds = Shape["kind"]; // automatically
// return type is the consituent of Shape that matches {kind: K}
function createShape<K extends Kinds>(kind: K): Extract<Shape, { kind: K }>;
function createShape(kind: Kinds): Shape {
switch (kind) {
case "circle":
return new Circle();
case "rectangle":
return new Rectangle();
case "square":
return new Square();
}
}
createShape("circle").radius; // okay
return 类型使用 Extract<T, U>
,一种 built-in 条件类型来过滤联合 T
以仅允许可分配给另一个类型 U
的成分。
请注意,我在 createShape()
中使用了 single-call-signature overload,因为编译器无法真正验证 switch
语句总是 return s 与未解析的条件类型 Extract<Shape, { kind: K}>
.
匹配的内容
希望对您有所帮助;祝你好运!
更新:你没有要求这个,但如果我正在编写一个像 createShape()
这样的函数,我可能会存储一个包含构造函数的对象,并使用索引访问让编译器在内部为我验证类型安全createShape()
:
const verifyShapeConstructors = <
T extends { [K in keyof T]: new () => { kind: K } }
>(
x: T
) => x;
const badShapeConstuctors = verifyShapeConstructors({
square: Square,
circle: Rectangle, // error!
rectangle: Circle, // error!
})
const shapeConstructors = verifyShapeConstructors({
square: Square,
rectangle: Rectangle,
circle: Circle
});
type ShapeConstructors = typeof shapeConstructors;
type Instance<T extends Function> = T["prototype"];
type Shape = Instance<ShapeConstructors[keyof ShapeConstructors]>;
type Kinds = keyof ShapeConstructors;
function createShape<K extends Kinds>(kind: K): Instance<ShapeConstructors[K]> {
return new shapeConstructors[kind]();
}
因为我正在使用辅助函数 verifyShapeConstructors()
来确保我不会弄乱 constructor-holding 对象键。我正在利用这样一个事实,即编译器知道像 Square
这样的 class 构造函数有一个 "prototype"
属性 实例类型......所以它可以使用索引访问 属性 以检查构造函数的实例类型。 (built-in 条件类型 InstanceType<C>
的行为类似,但编译器无法像推理索引访问那样推理条件类型)。
所有这些归结为一个事实,即 createShape()
现在是编译器验证为正确的 one-line 泛型函数。
正如我所说,您并没有要求这个,但它可能会引起一些兴趣。
有没有一个很好的方法可以让一个函数根据你传递给它的共享 属性 的值创建一个 class 的新实例,并让它 return 创建实例的具体类型,而不是联合类型?使用形状稍微调整打字稿文档示例:
class Square {
kind: "square";
size: number;
}
class Rectangle {
kind: "rectangle";
width: number;
height: number;
}
class Circle {
kind: "circle";
radius: number;
}
type Kinds = "circle" | "rectangle" | "square";
type Shape = Square | Rectangle | Circle;
function createShape(kind: Kinds) {
switch (kind) {
case "circle":
return new Circle();
case "rectangle":
return new Rectangle();
case "square":
return new Square();
}
}
createShape("circle").radius; //Property 'radius' does not exist on type 'Square | Rectangle | Circle'
例如,我可以在 Kinds
和 Shape
之间添加一个映射,并向函数添加一些类型注释:
type Kinds = "circle" | "rectangle" | "square";
type Shape = Square | Rectangle | Circle;
type ShapeKind = { "circle": Circle, "square": Square, "rectangle": Rectangle };
function createShape<T extends Kinds>(kind: T): ShapeKind[T] {
switch (kind) {
case "circle":
return new Circle();
case "rectangle":
return new Rectangle();
case "square":
return new Square();
}
}
createShape("circle").radius; //All good now
但是必须创建此映射感觉有点讨厌。我也可以使用类型保护,但这感觉很多余,因为我在创建和 return 时确定知道 Shape 的类型。有没有更好的方法来处理这个问题?
您不必创建映射;你可以从你的Shape
类型中提取它:
class Square {
readonly kind = "square";
size!: number;
}
class Rectangle {
readonly kind = "rectangle";
width!: number;
height!: number;
}
class Circle {
readonly kind = "circle";
radius!: number;
}
type Shape = Square | Rectangle | Circle;
type Kinds = Shape["kind"]; // automatically
// return type is the consituent of Shape that matches {kind: K}
function createShape<K extends Kinds>(kind: K): Extract<Shape, { kind: K }>;
function createShape(kind: Kinds): Shape {
switch (kind) {
case "circle":
return new Circle();
case "rectangle":
return new Rectangle();
case "square":
return new Square();
}
}
createShape("circle").radius; // okay
return 类型使用 Extract<T, U>
,一种 built-in 条件类型来过滤联合 T
以仅允许可分配给另一个类型 U
的成分。
请注意,我在 createShape()
中使用了 single-call-signature overload,因为编译器无法真正验证 switch
语句总是 return s 与未解析的条件类型 Extract<Shape, { kind: K}>
.
希望对您有所帮助;祝你好运!
更新:你没有要求这个,但如果我正在编写一个像 createShape()
这样的函数,我可能会存储一个包含构造函数的对象,并使用索引访问让编译器在内部为我验证类型安全createShape()
:
const verifyShapeConstructors = <
T extends { [K in keyof T]: new () => { kind: K } }
>(
x: T
) => x;
const badShapeConstuctors = verifyShapeConstructors({
square: Square,
circle: Rectangle, // error!
rectangle: Circle, // error!
})
const shapeConstructors = verifyShapeConstructors({
square: Square,
rectangle: Rectangle,
circle: Circle
});
type ShapeConstructors = typeof shapeConstructors;
type Instance<T extends Function> = T["prototype"];
type Shape = Instance<ShapeConstructors[keyof ShapeConstructors]>;
type Kinds = keyof ShapeConstructors;
function createShape<K extends Kinds>(kind: K): Instance<ShapeConstructors[K]> {
return new shapeConstructors[kind]();
}
因为我正在使用辅助函数 verifyShapeConstructors()
来确保我不会弄乱 constructor-holding 对象键。我正在利用这样一个事实,即编译器知道像 Square
这样的 class 构造函数有一个 "prototype"
属性 实例类型......所以它可以使用索引访问 属性 以检查构造函数的实例类型。 (built-in 条件类型 InstanceType<C>
的行为类似,但编译器无法像推理索引访问那样推理条件类型)。
所有这些归结为一个事实,即 createShape()
现在是编译器验证为正确的 one-line 泛型函数。
正如我所说,您并没有要求这个,但它可能会引起一些兴趣。