TypeScript泛型接口扩展报错:为何=与extends用法有差异?
=(默认值)与extends(约束)的差异分析 问题场景
原始代码
export interface ExecutionResult<TData = { [key: string]: any }, TExtensions = { [key: string]: any }> { errors?: ReadonlyArray<any>; data?: TData | null; extensions?: TExtensions; } export interface MyExecutionResult< TData = { [key: string]: any; }, > extends ExecutionResult { data?: TData | null; }
编译错误信息
Interface 'MyExecutionResult<TData>' incorrectly extends interface 'ExecutionResult<{ [key: string]: any; }, { [key: string]: any; }>'. Types of property 'data' are incompatible. Type 'TData | null | undefined' is not assignable to type '{ [key: string]: any; } | null | undefined'. Type 'TData' is not assignable to type '{ [key: string]: any; } | null | undefined'. Type 'TData' is not assignable to type '{ [key: string]: any; }'.ts(2430)
修改后可运行代码
export interface MyExecutionResult< TData extends { [key: string]: any; }, > extends ExecutionResult { data?: TData | null; }
核心疑问
为何仅使用泛型默认值(TData = ...)会报错,而添加extends约束后即可正常运行,二者的差异是什么?
差异解析
1. 泛型默认值(TData = ...)的作用
泛型默认值只是指定当调用者不主动传入泛型参数时,该参数的默认类型,但完全不限制调用者传入的类型范围。也就是说,你可以给MyExecutionResult传入任意类型作为TData,比如string、number甚至null。
在你的原始代码中,MyExecutionResult继承的ExecutionResult会使用自身的默认泛型参数,即ExecutionResult<{ [key: string]: any }, { [key: string]: any }>,此时父接口的data类型是{ [key: string]: any } | null | undefined。而子接口MyExecutionResult的data类型是TData | null | undefined——如果调用者传入TData = number,那么number类型显然无法赋值给{ [key: string]: any },这就违反了接口继承的类型兼容性规则,导致编译报错。
2. 泛型约束(TData extends ...)的作用
泛型约束是强制限制泛型参数必须是指定类型的子类型,也就是说,TData只能是{ [key: string]: any }或它的子类型(比如更具体的对象类型)。
添加约束后,任何合法的TData都必然可以赋值给{ [key: string]: any },此时子接口MyExecutionResult的data类型TData | null | undefined自然可以兼容父接口的data类型,满足接口继承的要求,因此编译通过。
内容的提问来源于stack exchange,提问作者Joey Yi Zhao

