首页 > 解决方案 > 为什么 Typescript 不能通过深度/嵌套属性推断 Switch 语句中的类型

问题描述

为什么类型推断在示例 A 中有效,但在示例 B 中无效?唯一的区别是类型字符串的位置。block.typeblock.meta.type。A 编译并推断类型和 B 结果。

// Example B errors
Property 'a' does not exist on type 'Block'. Property 'a' does not exist on type 'ITwo'.
Property 'b' does not exist on type 'Block'. Property 'b' does not exist on type 'IOne'.

如何在不更改IOneor的数据结构的情况下让 B 编译和正确推断ITwo


示例 A

https://www.typescriptlang.org/play?#code/KYOwrgtgBAKgmgBQKJQN5QPIDkUF4oDkA9iMAQDSwDqGU+BALgO5EEC+AUBwJYgPAAnAGYBDAMbAoASQyk0HKIqgiAXFADODAbwDm5BUoYBPAA7A18ZADpsSDpx59BoidJgt5SqACM1m7SB6BorGZhaISFYwNPZcoZIAQgA2RGIA1nTSspIAPm4sXADaALpWECImABSV3inpKsmpaQCUdAB8nkrqTNwMYgAWUDV1aVbxrajBXlBiIuqSlpG2KlPTSrVNViKraz4CwCJpANw7irPzsBFRNCu70xvpVt5Qp-f7hydenGzNR0A

enum TYPE { ONE = 'one', TWO = 'two'}

interface IOne {
    a: string,
    type: TYPE.ONE
}

interface ITwo {
    b: string,
    type: TYPE.TWO
}

type Block = IOne | ITwo

[].map((block:Block) => {
    switch (block.type) {
        case TYPE.ONE:
            block.a
            break;
        case TYPE.TWO:
            block.b 
            break;
    }
});

示例 B

https://www.typescriptlang.org/play?#code/KYOwrgtgBAKgmgBQKJQN5QPIDkUF4oDkA9iMAQDSwDqGU+BALgO5EEC+AUBwJYgPAAnAGYBDAMbAoASQyk0HKIqgiAXFADODAbwDm5BUojAGIqGtQGlShgE8ADsDXxkAOmxJLUTpx59BoiWkYFnkrACM1TW0QPU8jEzNQq2t7R1hEJBcYGk9vLlsHKAAhABsiMQBrOmlZSQAfIJYuAG0AXRcIETsACm6wssqVUvKKgEo6AD4kxXUmbgYxAAsoPoGKjuMRFwLgcYtkpTERdUlnTPcVTwPFfpGXESvrsIFgEQqAbkfFI5P012yMJdrslbpUXGEoF8QS83p8rN5Ru8gA

enum TYPE { ONE = 'one', TWO = 'two'}

interface IOne {
    a: string,
    meta : {
        type: TYPE.ONE
    }
}

interface ITwo {
    b: string,
    meta : {
        type: TYPE.TWO
    }
}

type Block = IOne | ITwo

[].map((block:Block) => {
    switch (block.meta.type) {
        case TYPE.ONE:
            block.a
            break;
        case TYPE.TWO:
            block.b 
            break;
    }
});

在此先感谢,J。

标签: typescript

解决方案


这在 TS repo 中的Nested Tagged Unions 问题下进行了讨论和跟踪。对您的问题的简短回答:在问题得到解决之前,您将无法做您想做的事情

类型保护和泛型

也就是说,您仍然可以通过类型保护和泛型的组合来实现这一点。类型保护将执行运行时检查嵌套值是否与预期类型匹配,并且泛型将删除一些样板文件,否则需要检查联合中的每个类型。

由于我们正在讨论的示例非常抽象,因此对于您的实际代码来说,这可能不是一个实用的解决方案。

假设只有少数类型,摆脱 switch 语句以支持条件返回(“早早返回,经常返回”)是有意义的。这将使使用类型保护变得微不足道:

enum TYPE { ONE = 'one', TWO = 'two'}

interface IOne {
    a: string,
    meta : {
        type: TYPE.ONE
    }
}

interface ITwo {
    b: string,
    meta : {
        type: TYPE.TWO
    }
}

type Block = IOne | ITwo;

export const isBlock = <T extends Block>(
  b: Block,
  metaType: TYPE,
): b is T =>
  b.meta.type === metaType;

[].map((block: Block) => {
    if (isBlock<IOne>(block, TYPE.ONE)) {
        return block.a;
    }
    
    return block.b; 
});

推荐阅读