TypeScript:如何定义参数继承Container的泛型函数且不使用any?
函数参数具有逆变特性,返回值具有协变特性。现在需要指定一个泛型函数类型,让它的参数继承Container类型且不使用any。当前代码调用process(processFn)时会报错,想知道怎么修正类型声明:
type Container<V> = { id: string; join: (value: V) => void; }; const process = <V extends Container<unknown>>(processFn: (container: V) => boolean) => { // do something with processFn } type ExtendedContainer = Container<number> & { status: string; }; const processFn = (container: ExtendedContainer) => { return true; } process(processFn); // 此处报错
报错原因分析
当前process的泛型约束是V extends Container<unknown>,TypeScript会将V推断为最宽泛的Container<unknown>,但传入的processFn参数类型是更具体的ExtendedContainer(Container<number>的扩展类型)。由于函数参数的逆变特性,要求函数参数类型必须“更宽泛”才能兼容,而ExtendedContainer比Container<unknown>更具体,因此类型不匹配导致报错。
解决方案
方案1:拆分泛型参数,明确Container的类型参数与扩展类型
通过两个泛型参数分别约束Container的类型参数T,以及继承自Container<T>的扩展类型C,让TypeScript能准确推断具体类型:
type Container<V> = { id: string; join: (value: V) => void; }; // T对应Container的类型参数,C是扩展Container<T>的具体类型 const process = <T, C extends Container<T>>(processFn: (container: C) => boolean) => { // do something with processFn } type ExtendedContainer = Container<number> & { status: string; }; const processFn = (container: ExtendedContainer) => { return true; } process(processFn); // 无报错,T被推断为number,C被推断为ExtendedContainer
方案2:利用逆变特性,直接约束参数为Container的子类型
更简洁的写法:只声明Container的类型参数T,利用逆变特性允许processFn的参数是Container<T>的任意子类型(逆变允许更具体的参数类型赋值给更宽泛的参数类型):
type Container<V> = { id: string; join: (value: V) => void; }; // T对应Container的类型参数,processFn的参数可以是Container<T>的任意子类型 const process = <T>(processFn: (container: Container<T>) => boolean) => { // do something with processFn } type ExtendedContainer = Container<number> & { status: string; }; const processFn = (container: ExtendedContainer) => { return true; } process(processFn); // 无报错,T被推断为number,ExtendedContainer是Container<number>的子类型,符合逆变规则
方案2的核心逻辑是:如果类型A是类型B的子类型,那么(a: A) => R可以赋值给(b: B) => R(逆变特性)。这里ExtendedContainer是Container<number>的子类型,因此对应的函数类型自然兼容(container: Container<number>) => boolean,TypeScript能自动完成类型推断。
内容的提问来源于stack exchange,提问作者steak_Overcooked

