【问题标题】:Return discriminated union from factory function in typescript从打字稿中的工厂函数返回可区分的联合
【发布时间】:2021-07-03 02:01:24
【问题描述】:

我正在尝试从工厂函数返回一个对象,并根据可区分的联合为其指定一个确切的类型。我在下面总结了一个非常简化的代码示例,但是当将鼠标悬停在 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

【问题讨论】:

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


    【解决方案1】:

    这是一个众所周知的问题。编译器无法根据泛型类型参数验证具体返回的类型是否与条件类型兼容。您可以在 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

    此类情况的常见 goto 是 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 调用实际上不会发生,因为 &amp;&amp; 的短循环性质。这看起来有点难看,而且我的口味需要很多仪式。

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

    【讨论】:

    • 非常感谢您提供令人难以置信的详细和具体的答案-一直在 github 上转来转去,在这里试图找到我的情况,但没有任何快乐,所以非常感谢您指出我正确的方向。
    猜你喜欢
    • 1970-01-01
    • 2021-11-19
    • 1970-01-01
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 2018-07-01
    • 1970-01-01
    • 2020-08-22
    相关资源
    最近更新 更多