提问人:MaxAxeHax 提问时间:3/13/2019 最后编辑:MaxAxeHax 更新时间:3/13/2019 访问量:8518
为什么我的 Typescript 对象上允许这个额外的属性?
Why is this extra property allowed on my Typescript object?
问:
我们最近开始在我们的 Web 平台项目中使用 typescript。
其中一大优势应该是强大的类型系统,它允许对各种正确性进行编译时检查(假设我们努力正确地建模和声明我们的类型)。
目前,我似乎已经找到了类型系统能够实现的极限,但它似乎不一致,我也可能只是使用了错误的语法。
我正在尝试对我们的应用程序将从后端接收的对象类型进行建模,并使用类型系统让编译器检查应用程序中的任何位置:
- 结构,即 TS 编译器只允许对某个类型的对象使用现有的(枚举的)属性
- 属性类型检查,即 TS 编译器知道每个属性的类型
这是我的方法的最小化版本(或直接链接到 TS playground )
interface DataObject<T extends string> {
fields: {
[key in T]: any // Restrict property keys to finite set of strings
}
}
// Enumerate type's DB field names, shall be used as constants everywhere
// Advantage: Bad DB names because of legacy project can thus be hidden in our app :))
namespace Vehicle {
export enum Fields {
Model = "S_MODEL",
Size = "SIZE2"
}
}
// CORRECT ERROR: Property "SIZE2" is missing
interface Vehicle extends DataObject<Vehicle.Fields> {
fields: {
[Vehicle.Fields.Model]: string,
}
}
// CORRECT ERROR: Property "extra" is not assignable
interface Vehicle2 extends DataObject<Vehicle.Fields> {
fields: {
extra: string
}
}
// NO ERROR: Property extra is now accepted!
interface Vehicle3 extends DataObject<Vehicle.Fields> {
fields: {
[Vehicle.Fields.Model]: string,
[Vehicle.Fields.Size]: number,
extra: string // Should be disallowed!
}
}
为什么第三个接口声明没有抛出错误,而编译器似乎完全能够在第二种情况下禁止无效的属性名称?
答:
3赞
James Monger
3/13/2019
#1
如果你想象这是一个这样的界面:fields
interface Fields {
Model: string;
Size: number;
}
(它是匿名完成的,但它确实与此接口匹配,因为您的[key in Vehicle.Fields]: any
)
然后这失败了,因为它与该接口不匹配 - 它没有 or 属性:Model
Size
fields: {
extra: string
}
但是,这通过:
fields: {
Model: string;
Size: number;
extra: string
}
因为匿名接口,所以有接口的扩展。它看起来像这样:Fields
interface ExtendedFields extends Fields {
extra: string;
}
这一切都是通过 TypeScript 编译器匿名完成的,但您可以向接口添加属性,并且仍然让它与接口匹配,就像扩展类仍然是基类的实例一样
评论
0赞
MaxAxeHax
3/13/2019
谢谢你的解释,它为我澄清了一些事情。我想我对创建类型化对象文字的情况感到困惑,其中编译器(当然)不允许对象添加不属于声明类型的属性。
9赞
Titian Cernicova-Dragomir
3/13/2019
#2
这是预期行为。基本接口仅指定最低要求是什么,打字稿中没有要求实现类字段和接口字段之间的精确匹配。出现错误的原因不是存在,而是缺少其他字段。(底部错误是field
Vehicle2
extra
Property 'S_MODEL' is missing in type '{ extra: string; }'.
)
如果使用条件类型存在这些额外属性,您可以执行一些类型技巧来获取错误:
interface DataObject<T extends string, TImplementation extends { fields: any }> {
fields: Exclude<keyof TImplementation["fields"], T> extends never ? {
[key in T]: any // Restrict property keys to finite set of strings
}: "Extra fields detected in fields implementation:" & Exclude<keyof TImplementation["fields"], T>
}
// Enumerate type's DB field names, shall be used as constants everywhere
// Advantage: Bad DB names because of legacy project can thus be hidden in our app :))
namespace Vehicle {
export enum Fields {
Model = "S_MODEL",
Size = "SIZE2"
}
}
// Type '{ extra: string; [Vehicle.Fields.Model]: string; [Vehicle.Fields.Size]: number; }' is not assignable to type '"Extra fields detected in fields implementation:" & "extra"'.
interface Vehicle3 extends DataObject<Vehicle.Fields, Vehicle3> {
fields: {
[Vehicle.Fields.Model]: string,
[Vehicle.Fields.Size]: number,
extra: string //
}
}
评论
0赞
MaxAxeHax
3/13/2019
非常酷的解决方案IMO。这有点骇人听闻,对于非类型系统的书来说并不容易理解:D例如,请问在哪里可以了解有关条件类型检查的更多信息?我想我在 TS 手册中没有看到任何这些内容。
1赞
Titian Cernicova-Dragomir
3/13/2019
@MaxAxeHax 嗯 ..不确定。我阅读了 PR 并虔诚地关注了 GitHub 项目。此外,我喜欢做很多实验,在这里回答问题有助于我积累知识。有官方文档,但这些文档的解释非常狭窄,更多的是对语言功能的简要描述,他们没有深入探讨你可以用它做什么有趣的事情。
评论