【问题标题】:How to find and disambiguate a union type from a list of union types in TypeScript?如何从 TypeScript 中的联合类型列表中查找联合类型并消除歧义?
【发布时间】:2018-01-12 20:46:45
【问题描述】:

我有一个基本的联合类型;

    type A = {type: "A"}
    type B = {type: "B"}
    type X =  A | B

我有一个函数可以在列表中找到具有相同类型的项目:

    function find(x: X, list: Array<X>) {
        return list.find(item => item.type === x.type)
    }

我希望这个函数的返回类型是与输入 x 匹配的 X 的特定子类型。也就是我想让find({type: "A"}, [{type: "A"}, {type: "B"}])返回一个A类型。

任何想法我可以如何做到这一点?


编辑:事实证明我正在处理的事情有点复杂。我有一个队列的概念,如果存在,我想将一个项目添加到一个批次中,否则我想将一个新批次加入队列:

    type A = { type: "A" }
    type B = { type: "B" }
    type X = A | B

    type Batch<T extends X> = { type: T["type"]; batch: Array<T> }
    type BatchA = Batch<A>
    type BatchB = Batch<B>
    type BatchTypes = BatchA | BatchB

    function find(x: X, list: Array<BatchTypes>) {
        return list.find(item => item.type === x.type)
    }

    function enqueue(x: X, queue: Array<BatchTypes>) {
        const result = find(x, queue)
        if (result) {
            result.batch.push(x)
        } else {
            queue.push({type: x.type, batch:[x]})
        }
    }

    let a: A
    let b: B
    let queue: Array<BatchTypes>

    enqueue(a, queue)
    enqueue(b, queue)

这里的问题在于enqueue,因为结果是两种类型的联合。当我尝试重载类型时,结果已正确解决,但find 的第一个参数存在问题并将新批次推送到队列:

    function find(x: A, list: Array<BatchTypes>): BatchA
    function find(x: B, list: Array<BatchTypes>): BatchB
    function find(x: X, list: Array<BatchTypes>) {
        return list.find(item => item.type === x.type)
    }

    function enqueue(x: A, queue: Array<BatchTypes>)
    function enqueue(x: B, queue: Array<BatchTypes>)
    function enqueue(x: X, queue: Array<BatchTypes>) {
        const result = find(x, queue)
        if (result) {
            result.batch.push(x)
        } else {
            queue.push({ type: x.type, batch: [x] })
        }
    }

如果有更好的方法来澄清这个问题,请告诉我。


鉴于@artem 的回答,我已经接近了:

    function find<T extends X>(x: T, list: Array<BatchTypes>): Batch<T> {
        return <Batch<T>>list.find(item => item.type === x.type)
    }

    function enqueue<T extends X>(x: T, queue: Array<BatchTypes>) {
        const result = find(x, queue)
        if (result) {
            result.batch.push(x)
        } else {
            queue.push({ type: x.type, batch: [x] })
        }
    }

queue.push 仍然存在问题。


也许这是演示当前问题的更简洁示例:

    type A = { type: "A" }
    type B = { type: "B" }
    type X = A | B

    let list: Array<Array<A> | Array<B>>

    function append<T extends X>(x: T) {
        list.push([x])
    }

    function append2(x: X) {
        list.push([x])
    }

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您可以通过为find 添加overload declarations 来做到这一点:

    type A = {type: "A"}
    type B = {type: "B"}
    type X = A | B
    
    function find(a: A, list: Array<X>): A;
    function find(a: B, list: Array<X>): B;
    function find(x: X, list: Array<X>) {
        return list.find(item => item.type === x.type)
    }
    
    let a: A;
    let b: B;
    let x = [a, b];
    
    let a1 = find(a, x); // inferred as A
    let b1 = find(b, x); // inferred as B
    

    如果find 返回类型将始终与其第一个参数的类型相同,您可以使用单个泛型重载声明来避免重复:

    function find<T extends X>(x: T, list: Array<X>): T;
    function find(x: X, list: Array<X>) {
        return list.find(item => item.type === x.type)
    }
    

    【讨论】:

    • 有趣。是否有任何方法可以遍历 X 中的所有联合类型以进行重载?
    • 我认为没有办法进行迭代,但是您可以使用单个泛型声明来表示 find 返回类型和第一个参数类型相同。我更新了答案。
    • 查看我刚刚添加的编辑——原来这只是其中的一部分......
    • 我不知道如何让 TypeScript 进行案例分析“如果 xA | B 然后 {type: x.type} 必须是 {type: 'A'} | {type: 'B'},而不是 {type: 'A' | 'B'} 没有明确switch 语句列出了代码中的所有备选方案。需要考虑更多。
    • 是的!这正是我正在争论的问题。感谢您的努力!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-26
    • 1970-01-01
    • 2021-10-06
    • 2019-01-12
    • 1970-01-01
    • 2020-02-25
    相关资源
    最近更新 更多