如何为TypeScript泛型可选函数参数获取正确类型推断?
问题描述
我写了一个合并两个对象的TypeScript函数,当第二个参数bar设为可选时,返回值的类型推断不符合预期,导致可以传入不存在的键值。代码如下:
function combine<F extends Record<string, string>, B extends Record<string, string>>(foo: F, bar?: B) { const retVal = { ...foo, ...bar } return retVal } const test = combine({ one: 'One', }, { two: 'Two', }) function testKey<T extends Record<string, string>>(foo: T, bar: keyof T) { } testKey(test, 'one') // 合法 testKey(test, 'two') // 合法 testKey(test, 'three') // 本应不合法,但实际合法
悬停在retVal上时,推断出的类型是:
const retVal: F & { [x: string]: string; }
这个类型限制过于宽松,允许任何字符串作为键。但如果把bar改成非可选参数,retVal的类型就会正确推断为F & B,这才是我想要的结果。现在需要找到在bar为可选参数时,也能得到正确类型推断的方法。
原因分析
TypeScript处理可选对象参数的展开时,因为bar可能是undefined,编译器无法确定它的具体键结构,会退化为宽泛的索引签名类型{[x:string]:string},而不是保留B的具体键信息。
解决方案
方案1:显式指定返回类型
直接将返回类型定义为F & (B | {}),覆盖bar为undefined时的情况(此时相当于合并空对象):
function combine<F extends Record<string, string>, B extends Record<string, string>>(foo: F, bar?: B): F & (B | {}) { return { ...foo, ...bar } as F & (B | {}) }
方案2:使用函数重载
通过重载分别定义bar存在和不存在的场景,让编译器精准推断类型:
// 重载1:不传bar时返回F类型 function combine<F extends Record<string, string>>(foo: F): F; // 重载2:传bar时返回F & B类型 function combine<F extends Record<string, string>, B extends Record<string, string>>(foo: F, bar: B): F & B; // 实现函数 function combine<F extends Record<string, string>, B extends Record<string, string>>(foo: F, bar?: B) { return { ...foo, ...bar } }
这个方案下,test的类型会被正确推断为{one: string} & {two: string},此时testKey(test, 'three')会直接报错,符合预期。
方案3:条件类型优化
用条件类型区分bar是否传入的情况,进一步精确返回类型:
function combine<F extends Record<string, string>, B extends Record<string, string> = {}>( foo: F, bar?: B ): B extends {} ? F & B : F { return { ...foo, ...bar } as any }
内容的提问来源于stack exchange,提问作者Ryan Palmer
相关产品推荐
相关产品推荐

