TypeScript中收窄对象key为单个属性时,为何value类型无法正确推断?
问题原因
这是TypeScript类型系统的当前限制:泛型函数里的泛型参数T代表keyof typeof obj的任意子集,当你通过key === 'a'收窄key的类型时,TypeScript只能识别出当前key是'a',但无法把泛型T约束为'a',自然也就没办法推断value的类型是obj['a']对应的string,仍然会把value当成string | number的联合类型。
解决方案
针对你的setValue需求,有几种可行的实现方式:
方法一:函数重载
通过定义重载签名,让TypeScript在调用时根据传入的key自动匹配对应的value类型,实现内部的类型收窄:
const obj = { a: "FOO", b: 123, } // 定义重载签名,明确每个key对应的value类型 function setValue(key: 'a', value: string): void; function setValue(key: 'b', value: number): void; // 实现签名,处理逻辑 function setValue(key: keyof typeof obj, value: string | number) { if (key === 'a') { // 用类型断言告诉TypeScript当前value是string value = value.toUpperCase() as string; } obj[key] = value; } setValue('a', 'bar'); // 执行后obj.a变为BAR setValue('b', 321); // 执行后obj.b变为321
方法二:自定义类型谓词
手动编写类型守卫函数,让TypeScript能通过key的判断来收窄value的类型:
const obj = { a: "FOO", b: 123, } // 自定义类型谓词,判断当前key是'a'时,value对应string类型 function isKeyA<T extends keyof typeof obj>(key: T, value: typeof obj[T]): value is typeof obj['a'] { return key === 'a'; } const setValue = <T extends keyof typeof obj>(key: T, value: typeof obj[T]) => { if (isKeyA(key, value)) { // 此时TypeScript能正确推断value是string value = value.toUpperCase(); } obj[key] = value; } setValue('a', 'bar'); setValue('b', 321);
方法三:使用区分联合类型作为参数
把key和value包装成一个区分联合类型的对象,让TypeScript自动根据key的值收窄对应的value类型:
const obj = { a: "FOO", b: 123, } // 定义区分联合类型,明确每个key对应的value类型 type SetValueParams = | { key: 'a', value: string } | { key: 'b', value: number }; const setValue = (params: SetValueParams) => { if (params.key === 'a') { params.value = params.value.toUpperCase(); } obj[params.key] = params.value; } setValue({ key: 'a', value: 'bar' }); setValue({ key: 'b', value: 321 });
补充说明
这几种方法都不需要修改原obj的结构,完全符合你的需求。其中函数重载最贴近你原来的调用方式,区分联合类型则让类型关联更直观,类型谓词适合需要复用判断逻辑的场景。
内容的提问来源于stack exchange,提问作者Zammy
相关产品推荐
相关产品推荐

