基于嵌套对象内属性的 Typescript Union

2024-03-31

我正在尝试根据对象中的嵌套属性创建联合类型。请参阅下面的示例:

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
}

当尝试根据嵌套对象内的属性设置 Union 时,如何解决此问题?


编译器不理解“嵌套可区分联合”的概念。如果联合的成员共享共同的“判别”属性,则类型是受判别联合。判别属性通常是单例/文字类型,例如true or "hello" or 123甚至null or undefined. You can't不过,使用另一个受歧视的联合作为判别器本身。如果可以的话那就太好了,因为这样受歧视的联合可以像您所做的那样从嵌套属性中传播。有一个建议位于微软/TypeScript#18758 https://github.com/microsoft/TypeScript/issues/18758允许这样做,但我没有看到任何动静。

就目前情况而言,类型TotalData不是一个受歧视的联盟。这只是一个联盟。这意味着编译器不会尝试处理类型的值TotalData as either Condition1 or Condition2。因此,如果您编写测试代码,您可能会遇到问题data.abilities.canManage并期望编译器理解其含义:

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

如果你想这样做,你可能会发现自己需要写用户定义的类型保护函数 https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards反而:

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

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

您在这里遇到的具体问题在哪里data被视为有效TotalData与如何有关超额财产检查 https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks被执行。 TypeScript 中的对象类型是“开放”/“可扩展”,而不是“封闭”/“exact https://github.com/microsoft/TypeScript/issues/12936”。您可以在不违反类型的情况下添加类型定义中未提及的额外属性。因此编译器不能完全禁止多余的属性;相反,它使用启发式方法来尝试找出这些属性何时是错误的以及何时是错误的。故意的。使用的规则主要是:如果您正在创建一个全新的对象字面量,并且它的任何属性未在其使用的类型中提及,则会出现错误。否则不会出现错误。

If TotalData是一个受歧视的工会,你会得到你期望的错误data因为data.abilities.canManage会导致编译器缩小范围data from TotalData to Condition2其中没有提到extraProp。但事实并非如此,所以data遗迹TotalData which does提到extraProp.

有人提出,在微软/TypeScript#20863 https://github.com/microsoft/TypeScript/issues/20863,对非歧视工会的超额财产检查应更加严格。我非常同意;混合和匹配不同联合成员的属性似乎不是常见的用例,因此警告可能会有所帮助。但同样,这是一个长期存在的问题,我没有看到任何进展。


为此,您可以做的一件事是更明确地说明您想要防范的多余属性。类型的值{a: string}可以有一个b类型的属性string,但是类型的值{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
}

编译器无法再协调data与任一Condition1 or Condition2,所以它抱怨。


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

Playground 代码链接 https://www.typescriptlang.org/play?#code/C4TwDgpgBAYg9nKBeKBvAUFLUCGAjASwBsDgCIBnALjU23oGMcA7AWRZwHMIa8EiILOlgC+AbmFQA9FKgQAHsABOOAApK4YAPw1mEAG4Ql02QFdmDOAFsrEZsCjAAFgQroR6T6EhQAQjmMUDHp8YlJyalp6RhZ2Zi4eKD44ASF6D3oFZTUNMF1TKzwjdy9waABhOGYAE3CqgEZkKOxQkjJKGmDorCY2Dm4aZVMISQ8RKAAyPwDSn0qauuYAJiaurFbwjubu3riEmgAzHCIKEfT3SdgEWegAFThgY4ARHEem+dqyBqgAHygPxZLTyWZgUBzVV44Gj3R5EF5vIImORKDRKACEkhkUAAtAA+KAAPyJBMxsluZSgAHJULhCG0Ip0oLt+okjicIGIoOI5IoVOpNPlCkZOSJKVBXFBmA9cBQKAROPE8AJHIhvNBKTDnpDKQA6SQbdqRNbRZnxAZQNmnAA0oxtmV5OQFUAArCV0AdzAwvsxxRQAd76gAKeTQh5ax4ASho8l9-yqnwI32NSggwFMSh98h1BoiOtNCQkHndnu9UCcNmDodh8JwEdokgIBygwezdM2FDzsRZdeN9CzWT5uR1wDgMAI8gg1UDEc5WKMqIx9CxhOJq+JUAAPNjt1AwHBZQQlSAoOZqhADgQ9NUtIvsOMIOzttF+w7+WBZ7J53AlKNPPRG82rj+omzBBvIEY9pIfY6gOjpgMOo7jpO04fpKiBfj+6RyI+vbYC+2RvqhGG-h4QA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于嵌套对象内属性的 Typescript Union 的相关文章

随机推荐