TypeScript 泛型方法多调用场景下的数组类型分配问题问询
你定义的Result<Key extends number> = Record<Key, string>的含义是必须包含所有Key联合类型中列出的键,且每个键的值为string。你类中的results被声明为Result<EveryKey>[],意味着数组里的每个元素必须同时包含EveryKey的所有键。但fetch方法传入的keys是EveryKey的子集,返回的Result<Key>只包含子集对应的键,完全满足不了Result<EveryKey>的要求,因此触发类型报错。
TS错误提示的核心逻辑是:Key是EveryKey的子类型,你试图把只包含Key对应键的对象,塞到要求包含所有EveryKey对应键的数组里,类型不匹配。比如EveryKey是1|2|3,Key是1|2,那Result<Key>没有键3,不符合Result<EveryKey>的约束。
你之前添加Result<never>能临时解决问题的原因是:Result<never>等价于空对象{},你把数组类型改成了(Result<EveryKey> | {})[],相当于把类型放宽到允许任意对象,自然不会报错,但这也失去了类型约束的意义,结构变更后就不再生效。
你的核心需求是results数组里的每个元素只能包含EveryKey范围内的键,允许任意子集,符合这个需求的最简方案是用Partial包装结果类型,修改后的代码如下:
type Result<Key extends number> = Record<Key, string>; function buildResult<Key extends number>(keys: readonly Key[]): Result<Key> { return keys.reduce((obj, key) => { return { ...obj, [key]: 'whatever' }; }, {} as Result<Key>); } class RequestPool<EveryKey extends number> { // 仅修改此处的results类型定义 private results: Partial<Result<EveryKey>>[]; constructor() { this.results = []; } fetch<Key extends EveryKey>(keys: readonly Key[]): Result<Key> { const result = buildResult(keys); this.results.push(result); return result; } }
Partial<Result<EveryKey>>等价于Partial<Record<EveryKey, string>>,含义是对象的键只能是EveryKey的成员,所有键都是可选的,刚好匹配你每次返回的子集对象结构。
如果buildResult返回更复杂的嵌套结构,对应调整results的类型即可,比如返回嵌套response的结构时,类型定义如下:
type BuildResult<Key extends number> = { response: Record<Key, string> } // 类内results定义 private results: {response: Partial<Record<EveryKey, string>>}[];
该方案完全满足你的所有需求:
- 调用
fetch时不需要额外声明泛型,Key会根据入参自动推导,返回值的键会被精确约束,不会允许访问未传入的键 results数组有严格的类型约束,只能存储符合要求的对象,访问数组元素的键时也只能输入EveryKey范围内的值,不会允许访问非法键
如果有极端需求需要精确追踪results数组里每个元素的具体键类型,可以借助TS可变元组泛型实现,但绝大多数业务场景下上述方案已经足够高效易用。
内容的提问来源于stack exchange,提问作者Elliot Winkler

