显式设置泛型时映射类型无法推断元组元素?有无解决办法?
我搞不懂为什么映射类型在显式指定泛型参数时失效,但用默认泛型却能完美推断出准确的参数元组?更关键的是,能不能在约束参数的同时保留默认泛型下的行为?本质上,有没有办法在约束参数的同时不丢失元组类型?
相关类型定义
type Selector<S = any, R = unknown> = (state: S) => R type SELCTOR_NAME = keyof SELECTOR_COLLECTION type SELECTOR_COLLECTION = { foo: string, bar: number } type Dependency<S = any> = SELCTOR_NAME | Selector<S>
场景与原函数定义
假设代码库中所有选择器都在SELECTOR_COLLECTION中有对应条目,我们需要实现一个生成缓存选择器的函数,它接收依赖项(可以是SELCTOR_NAME类型的字符串,也可以是Selector函数),返回对应的类型映射结果(为简化问题,这里仅做类型映射演示,实际会返回记忆化选择器)。
原函数定义如下:
declare function cachedSelector<G = any, T extends Dependency[] = Array<Dependency<G>>>( ...args: T ): { [I in keyof T]: T[I] extends SELCTOR_NAME ? () => SELECTOR_COLLECTION[T[I]] : T[I] }
函数参数的约束逻辑:
- 参数为
SELCTOR_NAME类型时,从SELECTOR_COLLECTION中提取对应类型,返回一个返回该类型的函数 - 参数为
Selector类型时,直接保留该类型
正常工作的情况
当不指定泛型参数时,TypeScript能完美推断元组中每个元素的具体类型:
// result 类型为 [() => string, () => number, () => boolean] const result = cachedSelector("foo", "bar", () => true)
问题场景
但显式指定泛型参数G(哪怕是默认值any)时,整个类型映射逻辑失效,返回类型退化为Dependency<any>[]:
// result 类型变为 Dependency<any>[] const result = cachedSelector<any>("foo", "bar", () => true) // 同时指定两个参数时行为相同 const resultBoth = cachedSelector<any, Dependency<any>[]>("foo", "bar", () => true)
问题原因
显式指定G后,TypeScript会触发T的默认值Array<Dependency<G>>,此时T被推断为数组类型而非具体元组类型。数组的元素类型是宽泛的Dependency<G>,导致条件类型T[I] extends SELCTOR_NAME无法匹配到具体的字符串字面量类型(比如"foo"),最终映射后的类型只能退化为基础约束类型。
解决方案
调整泛型参数的约束方式,去掉T的默认值,让TypeScript自动推断T为具体的元组类型,同时确保每个元素都符合Dependency<G>的约束:
declare function cachedSelector<G = any, T extends Dependency<G>[]>( ...args: T ): { [I in keyof T]: T[I] extends SELCTOR_NAME ? () => SELECTOR_COLLECTION[T[I]] : T[I] }
测试验证
修改后的函数在两种场景下都能正常工作:
// 自动推断:[() => string, () => number, () => boolean] const result1 = cachedSelector("foo", "bar", () => true) // 显式指定G后仍保留元组类型:[() => string, () => number, (state: any) => boolean] const result2 = cachedSelector<any>("foo", "bar", () => true) // 显式指定具体元组类型也能正常映射 const result3 = cachedSelector<any, ["foo", Selector<any, boolean>]>("foo", () => true)
原理说明
去掉T的默认值后,TypeScript会优先根据传入的参数推断T为具体的元组类型,同时检查元组中的每个元素是否符合Dependency<G>的约束。这样既保留了元组的类型细节,又实现了参数约束,映射类型也能正常匹配每个元素的具体类型。
内容的提问来源于stack exchange,提问作者Dellirium

