InstanceType工具类型异常:接受类型未定义额外属性问题
问题:InstanceType工具类型校验不严格的异常行为
我在使用InstanceType工具类型时遇到了异常行为。想要定义一个接受类构造函数及其对应实例的函数,示例代码如下:
export type Class<T=any> = { new(...args: any[]): T; }; function acceptClassAndInstance<T extends Class>(model: T, instance: InstanceType<T>) { }
问题在于,该函数不仅接受包含类型定义属性的对象,还会接受带有类型未定义额外属性的对象。完整示例如下:
export type Class<T=any> = { new(...args: any[]): T; }; class AModel { foo: string; constructor(params: Partial<Pick<AModel, keyof AModel>>) { Object.assign(this, params) } } class BModel { foo: string; bar: string; constructor(params: Partial<Pick<BModel, keyof BModel>>) { Object.assign(this, params) } } function acceptClassAndInstance<T extends Class>(model: T, instance: InstanceType<T>) { } /* 仅此情况应正常运行 */ const instance = new AModel({ foo: "bar" }) acceptClassAndInstance(AModel, instance) /* 本应报错但未报错 */ const instance2 = new BModel({ foo: "bar", bar: "baz"}) acceptClassAndInstance(AModel, instance2) /* 本应报错但未报错 */ const notAnInstance = { "foo": "bizz", "bar": "barrrrr", "anotherProperty": "should fail" } acceptClassAndInstance(AModel, notAnInstance) /* 此情况如预期报错 */ const notAnInstance2 = { "bar": "barrrrr", "anotherProperty": "should fail" } acceptClassAndInstance(AModel, notAnInstance2)
这是TypeScript的bug吗?有没有其他方法可以实现预期的校验行为?
解答
这不是TypeScript的bug
这是TypeScript结构类型系统的特性:只要一个对象包含目标类型的所有必填属性,即使存在额外属性,也会被判定为类型兼容。InstanceType<T>仅检查对象是否拥有类实例的所有属性,不会禁止额外属性存在。
实现严格校验的方法
方法1:自定义精确类型约束
定义一个精确类型工具,强制对象不能包含目标类型以外的属性:
// 精确类型工具:确保类型T与U完全匹配,无额外属性 type Exact<T, U> = T extends U ? (U extends T ? T : never) : never; export type Class<T=any> = { new(...args: any[]): T; }; function acceptClassAndInstance<T extends Class>( model: T, instance: Exact<InstanceType<T>, typeof instance> ) { }
此时传入带有额外属性的对象会直接触发类型错误。
方法2:利用私有属性实现名义类型
给类添加私有属性,让TypeScript通过名义而非结构区分类型:
export type Class<T=any> = { new(...args: any[]): T; }; class AModel { // 私有属性作为类型标识,只有AModel实例能匹配 private _brand: 'AModel'; foo: string; constructor(params: Partial<Pick<AModel, keyof AModel>>) { Object.assign(this, params) } } class BModel { private _brand: 'BModel'; foo: string; bar: string; constructor(params: Partial<Pick<BModel, keyof BModel>>) { Object.assign(this, params) } } function acceptClassAndInstance<T extends Class>(model: T, instance: InstanceType<T>) { }
这种方式下,instance2和notAnInstance都会因私有属性不匹配而报错,符合预期。
方法3:简化泛型约束并结合精确类型
修改函数泛型定义,直接关联类构造函数与实例类型,再结合精确类型工具:
type Exact<T, U> = T extends U ? (U extends T ? T : never) : never; function acceptClassAndInstance<T>( model: new (...args: any[]) => T, instance: Exact<T, typeof instance> ) { }
同样能实现严格的实例校验。
内容的提问来源于stack exchange,提问作者user6750100
相关产品推荐
相关产品推荐

