我正在尝试从工厂函数返回一个对象,并根据可区分的联合为其提供确切的类型。我在下面总结了一个非常简单的代码示例,但是当鼠标悬停在 newSquare 对象上时表明编译器知道它是一个 Square,我在 return new Circle of 时遇到错误:
<块引用>类型'Circle'不能分配给类型'DiscriminateUnion
谁能解释一下我做错了什么?非常感谢!
type ShapeKind = 'circle' | 'square'
class Circle{
kind: "circle" = 'circle';
radius: number;
constructor() {
this.radius = 4
}
}
class Square{
kind: "square" = 'square';
sideLength: number;
constructor() {
this.sideLength = 4
}
}
type Shape = Circle | Square;
type DiscriminateUnion<T, K extends keyof T, V extends T[K]> =
T extends Record<K, V> ? T : never
function makeShape<T extends ShapeKind>(shapeKind: T): DiscriminateUnion<Shape, 'kind', T> {
switch (shapeKind) {
case 'circle':
return new Circle()
case 'square':
return new Square()
default:
throw Error('not valid shape')
}
}
const newSquare = makeShape('square')
答案 0 :(得分:0)
这是一个众所周知的问题。编译器无法根据泛型类型参数验证具体返回类型是否与条件类型兼容。您可以在 microsoft/TypeScript/issues/33912 处了解确切的限制。
除了返回结果的明显类型断言:
function makeShape<T extends ShapeKind>(shapeKind: T): DiscriminateUnion<Shape, 'kind', T> {
switch (shapeKind) {
case 'circle':
return new Circle() as any // or `as DiscriminateUnion<Shape, 'kind', T>`
case 'square':
return new Square() as any
default:
throw Error('not valid shape')
}
}
这种情况下的常见跳转是function overloads:
function makeShape<T extends ShapeKind>(shapeKind: T): DiscriminateUnion<Shape, 'kind', T>
function makeShape(shapeKind: ShapeKind) : Shape {
switch (shapeKind) {
case 'circle':
return new Circle()
case 'square':
return new Square()
default:
throw Error('not valid shape')
}
}
/*
const newSquare: Square
*/
const newSquare = makeShape('square')
虽然这里没有错误,但实现函数实际上不会检查返回的类型是否与函数重载兼容,您可以完全错误地返回它:
function makeShape<T extends ShapeKind>(shapeKind: T): DiscriminateUnion<Shape, 'kind', T>
function makeShape(shapeKind: ShapeKind) : Shape {
switch (shapeKind) {
case 'circle':
return new Square() // wrong returned type but no error
case 'square':
return new Circle() // wrong returned type but no error
default:
throw Error('not valid shape')
}
}
试图强加严格的类型安全,您可以进一步尝试检查每个 case
分支内返回的类型兼容性。用于函数内类型检查的 brilliant idea 归功于 jcalz
:
function makeShape<T extends ShapeKind>(shapeKind: T): DiscriminateUnion<Shape, 'kind', T>
function makeShape(shapeKind: ShapeKind) : Shape {
switch (shapeKind) {
case 'circle':
const ret1 = new Square()
const tmp1: typeof ret1 = (false as true) && makeShape(shapeKind) // errors now
return ret1
case 'square':
const ret2 = new Circle()
const tmp2: typeof ret2 = (false as true) && makeShape(shapeKind) // errors now
return ret2
default:
throw Error('not valid shape')
}
}
它在运行时仍然有一些人工制品,虽然由于 makeShape
的短路性质,类型检查 &&
调用实际上不会发生。这看起来有点难看,而且我的口味需要很多仪式。
我相信非关键路径将重新调整的值注释为 as any
是完全可以接受的方式。