TypeScript Map getOrCreate 辅助函数类型不健全问题处理方案咨询
问题1:更优的实现方案
最优的解决方式是阻止TypeScript从valueFn参数推导泛型V的类型,强制仅从传入的Map实例推导V,从根源避免协变导致的类型放宽问题:
- 如果你使用 TypeScript 5.4 及以上版本,可以直接用内置的
NoInfer工具类型实现,修改后的代码如下:
export function mapGetOrCreate<K, V>(map: Map<K, V>, key: K, valueFn: (key: K) => NoInfer<V>): V { let value = map.get(key); if (value === undefined) { value = valueFn(key); assert(value !== undefined); map.set(key, value); } return value; }
这个版本在你的测试用例中调用mapGetOrCreate(m, '1', createA)会直接抛出类型错误,完全符合预期。
- 如果你使用低于5.4的TypeScript版本,可以自行实现
NoInfer,兼容性实现代码如下:
type NoInfer<T> = [T][T extends any ? 0 : never];
除此之外,你也可以用柯里化的写法拆分函数参数,先固定Map的类型:
export function mapGetOrCreate<K, V>(map: Map<K, V>) { return function(key: K, valueFn: (key: K) => V): V { let value = map.get(key); if (value === undefined) { value = valueFn(key); assert(value !== undefined); map.set(key, value); } return value; } } // 调用方式为 mapGetOrCreate(m)('1', createA),同样会触发正确的类型报错
问题2:mapGetOrCreate2的劣势
你修改后的版本确实能解决自动推导场景下的类型漏洞,但存在三个明显的劣势:
- 冗余泛型增加使用成本:多出来的
V2参数没有实际业务意义,如果遇到需要手动指定泛型的场景,需要多填一个参数,提升了使用门槛。 - 本质漏洞未修复:只要手动指定泛型参数
mapGetOrCreate2<string, B, A>(m, '1', createA),仍然可以绕开类型检查,没有从根源解决型变导致的不健全问题。 - 返回值丢失子类型信息:如果
valueFn返回的是V的子类型,这个版本的返回值固定为V,无法自动推导子类型的属性,后续使用子类型独有属性时需要额外做类型断言,增加了不必要的样板代码。
内容的提问来源于stack exchange,提问作者Kannan Goundan
相关产品推荐
相关产品推荐

