我们最近开始在网络平台项目中使用打字稿。
强大的键入系统应该是它的一大优点,它可以在编译时检查各种类型的正确性(假设我们努力地对模型进行正确的建模和声明)。
当前,我似乎已经发现类型系统能够实现的限制,但似乎不一致,而且我可能只是使用了错误的语法。
我正在尝试对我们的应用将从后端接收的对象类型进行建模,并使用类型系统让编译器在应用中的所有位置检查以下内容:
这是我的方法的最小化版本(或采用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!
}
}
当第二种情况下编译器似乎完全能够拒绝无效的属性名称时,为什么第三个接口声明不引发错误?
答案 0 :(得分: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编译器匿名完成的,但是您可以向接口添加属性,并使属性与接口匹配,就像扩展类仍然是基类的实例一样
答案 1 :(得分: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 //
}
}