基于嵌套对象内属性的打字稿联合
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
甚至null
或undefined
。但是,您不能使用另一个可区分的联合作为判别器本身。如果可以,那就太好了,因为这样可区分的联合可以像您正在做的那样从嵌套属性向上传播。 microsoft/TypeScript#18758 有一个建议允许这样做,但我看不到那里有任何动静。
就目前而言,TotalData
类型不是 可区分的 联合。这只是一个工会。这意味着编译器不会尝试将TotalData
类型的值视为专有 Condition1
或Condition2
。因此,如果您编写测试 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
会导致编译器将data
从TotalData
缩小到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
编译器无法再协调data
与Condition1
或Condition2
,因此它会报错。
好的,希望对您有所帮助;祝你好运!
Playground link to code
【讨论】:
感谢您的深入回复!真的帮助我了解发生了什么,我真的很感激 这可能会在 4.2 中得到解决! github.com/microsoft/TypeScript/pull/38839以上是关于基于嵌套对象内属性的打字稿联合的主要内容,如果未能解决你的问题,请参考以下文章