从打字稿中的工厂函数返回可区分的联合

时间:2021-07-03 02:01:24

标签: typescript factory typescript-generics return-type discriminated-union

我正在尝试从工厂函数返回一个对象,并根据可区分的联合为其提供确切的类型。我在下面总结了一个非常简单的代码示例,但是当鼠标悬停在 newSquare 对象上时表明编译器知道它是一个 Square,我在 return new Circle of 时遇到错误:

<块引用>

类型'Circle'不能分配给类型'DiscriminateUnion | DiscriminateUnion'。 类型 'Circle' 不能分配给类型 'DiscriminateUnion'。(2322)

谁能解释一下我做错了什么?非常感谢!

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')

Link to typescript playground

1 个答案:

答案 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')
    }
}

playground link

这种情况下的常见跳转是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')

playground link

虽然这里没有错误,但实现函数实际上不会检查返回的类型是否与函数重载兼容,您可以完全错误地返回它:

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')

    }
}

playground link

试图强加严格的类型安全,您可以进一步尝试检查每个 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')

    }
}

playground link

它在运行时仍然有一些人工制品,虽然由于 makeShape 的短路性质,类型检查 && 调用实际上不会发生。这看起来有点难看,而且我的口味需要很多仪式。

我相信非关键路径将重新调整的值注释为 as any 是完全可以接受的方式。

相关问题