基于嵌套对象内的属性的打字稿联合 [英] Typescript Unions based on property inside nested object

查看:21
本文介绍了基于嵌套对象内的属性的打字稿联合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to create a Union Type based on a nested property in my object. See the example below:

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 值为真,我只对允许某些属性感兴趣.这在嵌套时似乎不起作用.但如果我有这样的东西而不嵌套,那就没问题了:

The issue I'm having is that typescript ignores the conditions I set. I'm interested in only allowing certain properties if the canMange value is true. This doesn't seem to work when it's nested. But if I had simply something like this without nesting, it would be fine:

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
}

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

How can I go about solving this issue when trying to set a Union based on the property inside a nested object?

推荐答案

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

The compiler doesn't understand the concept of "nested discriminated unions". A type is a discriminated union if the members of the union share a common "discriminant" property. A discriminant property is generally a singleton/literal type like true or "hello" or 123 or even null or undefined. You can't use another discriminated union as a discriminant itself, though. It would be nice if you could, because then discriminated unions could propagate up from nested properties the way you're doing. There's a suggestion at microsoft/TypeScript#18758 to allow this, but I don't see any movement there.

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

As it stands, the type TotalData isn't a discriminated union. It's just a union. That means the compiler will not try to treat a value of type TotalData as exclusively either Condition1 or Condition2. So you will probably run into issues if you write code that tests data.abilities.canManage and expects the compiler to understand the implications:

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

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

If you want to do this you might find yourself needing to write user-defined type guard functions instead:

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

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

<小时>

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


The specific issue you're running into here, where data is seen as a valid TotalData has to do with how excess property checking is performed. Object types in TypeScript are "open"/"extendable", not "closed"/"exact". You are allowed to add extra properties unmentioned in a type's definition without violating the type. So the compiler can't prohibit excess properties completely; instead, it uses heuristics to try to figure out when such properties are a mistake and when they are intentional. The rule used is mostly: if you are creating a brand new object literal and any of its properties are not mentioned in the type of where it's being used, there will be an error. Otherwise there won't be.

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

If TotalData were a discriminated union, you'd get the error you expect on data because data.abilities.canManage would cause the compiler to narrow data from TotalData to Condition2 which doesn't mention extraProp. But it's not, and so data remains TotalData which does mention extraProp.

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

It's been proposed, in microsoft/TypeScript#20863, that excess property checking be stricter for non-discriminated unions. I pretty much agree; mixing and matching properties from different union members doesn't seem to be a common use case, so a warning would probably be helpful. But again, this is a longstanding issue and I don't see any movement there.

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

One thing you can do for this is to be more explicit about excess properties you'd like to guard against. A value of type {a: string} can have a b property of type string, but a value of type {a: string, b?: never} cannot. So this latter type will prevent properties of type b without relying on the compiler's heuristics for excess property checking.

就你而言:

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

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

will behave very similarly to your original Foo definition, but now you get this error:

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

编译器无法再将 dataCondition1Condition2 协调起来,所以它会报错.

The compiler can no longer reconcile data with either Condition1 or Condition2, so it complains.

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

Okay, hope that helps; good luck!

代码的游乐场链接

这篇关于基于嵌套对象内的属性的打字稿联合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆