TypeScript泛型基类与映射类型:初始化及约束错误求助
代码文件内容
AddShiftForm.ts
type AddShiftFormInputDef = { "start": BehaviorSubject<{value: Date , valid: boolean}>, "end": BehaviorSubject<{value: Date , valid: boolean}>, "name": BehaviorSubject<{value: string, valid: boolean}> } export class AddShiftForm extends AbstractGqlForm<AddShiftFormInputDef>{ constructor(requiredInputs:RequiredInputs) { super(requiredInputs) } }
BaseGqlForm.ts
export abstract class AbstractGqlForm<T extends {[Property in keyof T]: BehaviorSubject<GqlFormInputStatus> }>{ protected inputSubjects: {[Property in keyof T]: BehaviorSubject<any>} | undefined; protected requiredInputs: RequiredInputs; protected constructor(requiredInputs:RequiredInputs) { this.requiredInputs = requiredInputs; } registerInput(inputName: keyof T, input$: BehaviorSubject<GqlFormInputStatus>) { if( this.inputSubjects === undefined){ this.inputSubjects = {inputName: input$}; }else{ this.inputSubjects[inputName] = input$; } } }
GqlFormInputStatus.ts
import {BaseGqlInput} from "./BaseGqlInputTypes"; export interface GqlFormInputStatus { value: BaseGqlInput | undefined , valid: boolean } export interface TypedGqlFormInputStatus<T extends BaseGqlInput> { value: T | undefined, valid: boolean }
BaseGqlInputTypes.ts
export type BaseGqlInput = string | number | Date;
遇到的两个问题
问题1:继承时类型约束不匹配
AddShiftForm继承AbstractGqlForm时触发TypeScript错误:
TS2344: Type 'AddShiftFormInputDef' does not satisfy the constraint '{ start: BehaviorSubject
; end: BehaviorSubject ; name: BehaviorSubject ; }'.
The types of '"start".observers' are incompatible between these types.
Type 'Observer<{ value: Date; valid: boolean; }>[]' is not assignable to type 'Observer[]'.
Type 'Observer<{ value: Date; valid: boolean; }>' is not assignable to type 'Observer'
问题2:inputSubjects初始化与赋值错误
直接初始化报错:
protected inputSubjects: {[Property in keyof T]: BehaviorSubject<any>} = {};TS2322: Type '{}' is not assignable to type '{ [Property in keyof T]: BehaviorSubject
; }'. 在
registerInput中初始化时:this.inputSubjects = {inputName: input$};TS2322: Type '{ inputName: BehaviorSubject
; }' is not assignable to type '{ [Property in keyof T]: BehaviorSubject ; }'.
Object literal may only specify known properties, and 'inputName' does not exist in type '{ [Property in keyof T]: BehaviorSubject; }'.
解决方案
解决问题1:统一类型约束
问题核心是AddShiftFormInputDef中BehaviorSubject的泛型是具体结构,而基类约束的是GqlFormInputStatus,TypeScript默认不认为结构兼容的匿名类型与接口是同一类型。
修改步骤:
- 用
TypedGqlFormInputStatus定义具体输入类型,确保与GqlFormInputStatus的继承关系 - 调整基类泛型约束,兼容子类型:
修改AbstractGqlForm:
export abstract class AbstractGqlForm<T extends {[K in keyof T]: BehaviorSubject<TypedGqlFormInputStatus<infer V>>} where V extends BaseGqlInput>{ protected inputSubjects: Partial<{[K in keyof T]: T[K]}> = {}; protected requiredInputs: RequiredInputs; protected constructor(requiredInputs: RequiredInputs) { this.requiredInputs = requiredInputs; } registerInput<K extends keyof T>(inputName: K, input$: T[K]) { this.inputSubjects[inputName] = input$; } }
修改AddShiftFormInputDef:
type AddShiftFormInputDef = { "start": BehaviorSubject<TypedGqlFormInputStatus<Date>>, "end": BehaviorSubject<TypedGqlFormInputStatus<Date>>, "name": BehaviorSubject<TypedGqlFormInputStatus<string>> }
解决问题2:inputSubjects初始化与赋值
- 直接初始化:用
Partial将属性转为可选,避免空对象不匹配类型的问题,如上面代码所示。 - registerInput赋值:原代码错误在于用字面量
inputName作为键,需改为计算属性名,配合Partial类型无需额外断言即可直接赋值。
内容的提问来源于stack exchange,提问作者user3241778

