TypeScript嵌套泛型场景下this类型自动交叉导致报错求解
TypeScript 泛型
this类型自动交叉引发的报错问题 问题表现
编写TypeScript代码时遇到不符合预期的类型报错,具体现象如下:
- 定义基础
Model类及关联的泛型配置类型后,继承Model的子类编写options配置项时,只要ModelIndex是通过{}定义的对象类型,就会触发类型报错 - 移除
indexes配置、或者将indexes的类型改为非对象包裹的形式时,报错自动消失,表现和常规类型认知不符 - 调试时发现
ModelIndex的泛型参数被自动推断为M & Model,而非预期传入的M,不清楚TypeScript自动做类型交叉处理的逻辑
复现代码如下:
class Model { fields!: ModelFields; options!: ModelOptions<this>; } class ModelWithoutIndex { fields!: ModelFields; options!: ModelOptionsWithoutIndex<this>; } class ModelWithIndexNotInBrackets { fields!: ModelFields; options!: ModelOptionsNotInBracket<this>; } class User extends Model { fields = { a: FieldTypes.BOOL } // 此处触发类型报错 options: ModelOptions<this> = { indexes: [{ unique: true, fields: ['a'] }] } } class UserWithoutIndex extends ModelWithoutIndex { fields = { a: FieldTypes.BOOL } // 此处无报错:仅当ModelIndex是{}定义的对象类型时才会报错 options: ModelOptionsWithoutIndex<this> = { } } class UserWithIndexNotInBrackets extends ModelWithIndexNotInBrackets { fields = { a: FieldTypes.BOOL } // 此处无报错,和预期认知不符 options: ModelOptionsNotInBracket<this> = { } } // ------------ 类型定义 ---------------- enum FieldTypes { CHAR = 'CHAR', BOOL = 'BOOL', INT = 'INT' } type ModelIndex<M extends Model> = { unique: true fields: (keyof M["fields"])[] } type ModelIndexesNotInBracket<M extends ModelWithIndexNotInBrackets> = (keyof M["fields"])[] type OrderingOfModelOptions<M extends Model | ModelWithoutIndex | ModelWithIndexNotInBrackets> = keyof M["fields"]| keyof { [F in keyof M["fields"] as F extends string ? `-${F}` : never] : 1} type ModelOptionsNotInBracket<M extends ModelWithIndexNotInBrackets> = { indexes?: ModelIndexesNotInBracket<M>[]; ordering?: OrderingOfModelOptions<M>[]; } type ModelOptionsWithoutIndex<M extends ModelWithoutIndex> = { ordering?: OrderingOfModelOptions<M>[]; } type ModelOptions<M extends Model> = { indexes?: ModelIndex<M>[]; ordering?: OrderingOfModelOptions<M>[]; } type ModelFields = { [field: string]: FieldTypes }
报错根因
这个问题由TypeScript类中 多态this类型的检查规则 导致:
- 类内定义的
options!: ModelOptions<this>属于多态this类型,TypeScript做静态检查时,要求子类赋值给options的值必须兼容所有可能的派生类的this类型,而非仅匹配当前子类的this - 当
ModelIndex为对象类型时,TypeScript会对对象字面量执行严格的泛型约束校验:由于泛型参数要求M extends Model,TS会自动给传入的this类型加上& Model的交叉约束,确保类型满足泛型边界要求 - 子类中重写的
fields属性类型是在类实例化阶段才完全确定,在类属性初始化的检查阶段,TS无法确定M & Model下fields的具体key值,因此判定['a']无法匹配(keyof M["fields"])[]类型,触发报错 - 另外两种无报错场景的原因:
- 未配置
indexes时:不会触发ModelIndex的对象类型检查,不会走到泛型边界交叉的推导逻辑 indexes类型为非对象包裹的数组类型时:TS对数组字面量的约束校验不会触发额外的泛型边界交叉推导,因此不会报错
- 未配置
修复方案
两种常用的修复方式可按需选择:
- 方案1:直接给
options属性指定当前子类的类型,绕过多态this对全派生类的兼容检查class User extends Model { fields = { a: FieldTypes.BOOL } options: ModelOptions<User> = { indexes: [{ unique: true, fields: ['a'] }] } } - 方案2:通过
ThisType工具类型调整泛型定义,让TypeScript延迟推导this类型,跳过属性初始化阶段的交叉检查
调整后子类无需修改原有type ModelOptions<M extends Model> = { indexes?: ModelIndex<M>[]; ordering?: OrderingOfModelOptions<M>[]; } & ThisType<M>ModelOptions<this>的写法即可正常通过类型检查。
内容的提问来源于stack exchange,提问作者Nicolas Leal
相关产品推荐
相关产品推荐

