基于嵌套对象内属性的打字稿联合

Posted

技术标签:

【中文标题】基于嵌套对象内属性的打字稿联合【英文标题】:Typescript Unions based on property inside nested object 【发布时间】:2020-08-27 03:15:59 【问题描述】:

我正在尝试基于对象中的嵌套属性创建联合类型。请看下面的例子:

type Foo = 
    abilities: 
        canManage: boolean
    


type Bar = 
    abilities: 
        canManage: boolean
    
    extraProp: number


type Condition1 = 
    abilities: 
        canManage: true
    
 & Bar

type Condition2 = 
    abilities: 
        canManage: false
    
 & Foo

type TotalData = Condition1 | Condition2

const data: TotalData = 
    abilities: 
        canManage: false, // if canManage is false, TS should complain when I add the `extraProp` key
    ,
    extraProp: 5

我遇到的问题是打字稿忽略了我设置的条件。如果 canMange 值为 true,我只对允许某些属性感兴趣。这在嵌套时似乎不起作用。但是,如果我只是有这样的东西而没有嵌套,那就没问题了:

type Foo = 
     canManage: boolean


type Bar = 
    canManage: boolean
    extraProp: number


type Condition1 = 
    canManage: true
 & Bar

type Condition2 = 
    canManage: false
 & Foo
]
type TotalData = Condition1 | Condition2

const data: TotalData = 

canManage: false,
extraProp: 5 // now typescript complains that this property shouldn't be here because canManage is false

在尝试基于嵌套对象内的属性设置联合时,我该如何解决这个问题?

【问题讨论】:

type Foo = abilities: canManage: boolean ; extraProp?: never 会满足您的需求吗?仅在特定情况下才禁止额外的属性,这似乎不是其中之一。如果这对你有用,我会写一个答案;如果不是,请解释什么不适用于您的用例。祝你好运! 该解决方案可能暂时可行,但它有点 hacky,因为这是 TypeScript 的一个真正问题,您可能应该提交一个错误报告。 4.2 可能会解决这个问题:github.com/microsoft/TypeScript/pull/38839 【参考方案1】:

编译器不理解“嵌套的可区分联合”的概念。如果联合的成员共享一个共同的“判别”属性,那么一个类型就是一个判别联合。判别属性通常是单例/文字类型,如true"hello"123 甚至nullundefined。但是,您不能使用另一个可区分的联合作为判别器本身。如果可以,那就太好了,因为这样可区分的联合可以像您正在做的那样从嵌套属性向上传播。 microsoft/TypeScript#18758 有一个建议允许这样做,但我看不到那里有任何动静。

就目前而言,TotalData 类型不是 可区分的 联合。这只是一个工会。这意味着编译器不会尝试将TotalData 类型的值视为专有 Condition1Condition2。因此,如果您编写测试 data.abilities.canManage 的代码并希望编译器理解其含义,您可能会遇到问题:

function hmm(x: TotalData) 
    if (x.abilities.canManage) 
        x.extraProp.toFixed(); // error!
    //  ~~~~~~~~~~~ <--- possibly undefined?!
     

如果你想这样做,你可能会发现自己需要写user-defined type guard functions:

function isCondition1(x: TotalData): x is Condition1 
    return x.abilities.canManage;


function hmm(x: TotalData) 
    if (isCondition1(x)) 
        x.extraProp.toFixed(); // okay!
     


您在此处遇到的具体问题是data 被视为有效的TotalData,这与excess property checking 的执行方式有关。 TypeScript 中的对象类型是“开放的”/“可扩展的”,而不是“封闭的”/“exact”。您可以在不违反类型的情况下添加类型定义中未提及的额外属性。所以编译器不能完全禁止多余的属性;相反,它使用启发式方法来尝试找出这些属性何时是错误的,何时是故意的。使用的规则主要是:如果您正在创建一个全新的对象字面量,并且在使用它的类型中未提及其任何属性,则会出现错误。否则就没有了。

如果TotalData 是一个有区别的联合,你会在data 上得到你所期望的错误,因为data.abilities.canManage 会导致编译器将dataTotalData 缩小到Condition2,这没有提到extraProp。但事实并非如此,所以data 仍然是TotalData确实提到了extraProp

在microsoft/TypeScript#20863 中建议,对于非歧视性联合,过多的属性检查更加严格。我非常同意;来自不同联合成员的混合和匹配属性似乎不是一个常见的用例,因此警告可能会有所帮助。但同样,这是一个长期存在的问题,我没有看到任何动静。


为此,您可以做的一件事是更明确地说明您要防范的多余属性。 a: string 类型的值可以具有 string 类型的 b 属性,但 a: string, b?: never 类型的值不能。因此,后一种类型将阻止 b 类型的属性,而无需依赖编译器的启发式方法进行过多的属性检查。

在你的情况下:

type Foo = 
    abilities: 
        canManage: boolean
    ;
    extraProp?: never

将与您最初的 Foo 定义非常相似,但现在您收到此错误:

const data: TotalData =  // error!
// -> ~~~~
// Type ' abilities:  canManage: false; ; extraProp: number; '
// is not assignable to type 'TotalData'.
    abilities: 
        canManage: false,
    ,
    extraProp: 5

编译器无法再协调dataCondition1Condition2,因此它会报错。


好的,希望对您有所帮助;祝你好运!

Playground link to code

【讨论】:

感谢您的深入回复!真的帮助我了解发生了什么,我真的很感激 这可能会在 4.2 中得到解决! github.com/microsoft/TypeScript/pull/38839

以上是关于基于嵌套对象内属性的打字稿联合的主要内容,如果未能解决你的问题,请参考以下文章

将打字稿接口属性类型转换为联合[重复]

为啥打字稿将联合中的属性标记为不存在?

打字稿中具有联合类型键的松散类型对象

打字稿:对象和原语之间的keyof typeof联合总是永远不会

字符串联合的打字稿并发症

打字稿从元组/数组值派生联合类型