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

Sho*_*r91 1 typescript typescript-typings react-typescript

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

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
}
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

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

jca*_*alz 5

编译器不理解“嵌套歧视联合”的概念。如果联合的成员共享共同的“判别”属性,则类型是判别联合。判别属性通常是一个单/文字类型等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?!
    } 
}
Run Code Online (Sandbox Code Playgroud)

如果你想这样做,你可能会发现自己需要编写用户定义的类型保护函数

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

function hmm(x: TotalData) {
    if (isCondition1(x)) {
        x.extraProp.toFixed(); // okay!
    } 
}
Run Code Online (Sandbox Code Playgroud)

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

如果TotalData是一个歧视工会,你会得到你所期望的错误data,因为data.abilities.canManage会导致编译器来缩小dataTotalDataCondition2没有提及extraProp。但它不是,所以data遗体TotalDataextraProp

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


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

在你的情况下:

type Foo = {
    abilities: {
        canManage: boolean
    };
    extraProp?: never
}
Run Code Online (Sandbox Code Playgroud)

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

const data: TotalData = { // error!
// -> ~~~~
// Type '{ abilities: { canManage: false; }; extraProp: number; }'
// is not assignable to type 'TotalData'.
    abilities: {
        canManage: false,
    },
    extraProp: 5
}
Run Code Online (Sandbox Code Playgroud)

编译器无法再dataCondition1或协调Condition2,因此它会抱怨。


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

Playground 链接到代码

  • 这个问题可能会在 4.2 中得到解决!https://github.com/microsoft/TypeScript/pull/38839 (2认同)