为啥我的 Typescript 对象允许这个额外的属性?
Posted
技术标签:
【中文标题】为啥我的 Typescript 对象允许这个额外的属性?【英文标题】:Why is this extra property allowed on my Typescript object?为什么我的 Typescript 对象允许这个额外的属性? 【发布时间】:2019-08-03 21:25:24 【问题描述】:我们最近开始在我们的网络平台项目中使用 typescript。
一个巨大的优势应该是强大的类型系统,它允许在编译时检查各种正确性(假设我们努力正确地建模和声明我们的类型)。
目前,我似乎发现了类型系统所能达到的极限,但似乎不一致,我也可能只是使用了错误的语法。
我正在尝试对我们的应用将从后端接收的对象类型进行建模,并使用类型系统让编译器检查应用中的所有位置:
-
结构,即 TS 编译器只允许对某个类型的对象使用现有(枚举)属性
属性类型检查,即 TS 编译器知道每个属性的类型
这是我的方法的最小化版本(或采取direct link to 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!
为什么第三个接口声明没有抛出错误,而编译器似乎完全能够在第二种情况下禁止无效的属性名称?
【问题讨论】:
【参考方案1】:这是预期的行为。基本接口仅指定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 //
【讨论】:
非常酷的解决方案 IMO。对于非类型系统的书呆子来说,这有点 hacky 并且不容易理解:D 请问我可以在哪里了解更多关于条件类型检查的信息?我想我在 TS 手册中没有看到任何内容。 @MaxAxeHax 嗯 .. 不确定。我阅读了 PR 并虔诚地关注 GitHub 项目。我也喜欢做很多实验,在这里回答问题可以帮助我积累知识。有官方文档,但它们的解释非常狭窄,更多的是对语言功能的简要描述,它们没有涉及你可以用它做什么有趣的事情。【参考方案2】:如果你想象fields
是这样一个接口:
interface Fields
Model: string;
Size: number;
(它是匿名完成的,但由于您的[key in Vehicle.Fields]: any
,它确实与此接口匹配)
然后这会失败,因为它不匹配那个接口 - 它没有 Model
或 Size
属性:
fields:
extra: string
但是,这通过了:
fields:
Model: string;
Size: number;
extra: string
因为匿名接口存在Fields
接口的扩展。它看起来像这样:
interface ExtendedFields extends Fields
extra: string;
这一切都是通过 TypeScript 编译器匿名完成的,但是您可以向接口添加属性并使其仍然与接口匹配,就像扩展类仍然是基类的实例一样
【讨论】:
感谢您的解释,它为我解决了一些问题。我想我对创建类型化对象文字的情况感到困惑,其中编译器(当然)不允许对象添加不属于声明类型的属性。以上是关于为啥我的 Typescript 对象允许这个额外的属性?的主要内容,如果未能解决你的问题,请参考以下文章
返回具有额外属性的 arg 的 TypeScript 函数 (TS2322)
为啥允许使用 kebab-case 非标准属性,而不允许使用其他属性?以及如何在 TypeScript 中定义这样的类型?