TypeScript 4.4.4类型窄化未自动推导索引属性值类型报错问题
这不是写法疏漏,是TypeScript的类型推导存在明确边界:控制流分析无法自动为判别联合类型的多个字段建立跨字段的类型关联。
你定义的联合类型IFiltersNumber | IFiltersString中,两个接口的name字段都被标注为宽泛的keyof IRow,并没有和同结构下的value类型做绑定。当你通过typeof f.value === 'string'窄化f的类型时,TS只能确定f.value是string类型,但依然认为f.name可以是IRow的任意键(包括对应number类型的id、tax、total),因此row[name]的推导结果始终是string | number,无法匹配doesStringContainsKeyword要求的string参数类型。
你当前的类型定义本身还存在类型漏洞:传入{name: 'tax', value: 'Dan'}这种明显不符合业务逻辑的参数时,TS不会抛出任何错误,本质就是因为name和value的类型没有做绑定。另外你把f.name提前赋值给独立变量name的写法,也会让变量丢失和原对象f的窄化绑定关系,进一步阻碍TS的类型推导。
方案1:重构过滤接口,建立字段类型关联(推荐,类型安全性最高)
先提取IRow中对应值类型的键,再精准约束两个过滤接口的name类型:
type Operators = '>' | '<' | '>=' | '<=' | '===' interface IRow { id: number, customer: string, address: string, order: string, tax: number, total: number, } // 提取对象中匹配指定值类型的键 type KeysOfType<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T] interface IFiltersString { name: KeysOfType<IRow, string>, // 仅允许值为string类型的键 value: string, } interface IFiltersNumber { name: KeysOfType<IRow, number>, // 仅允许值为number类型的键 value: number, op: Operators }
修改后去掉提前赋值name的逻辑,直接在分支中使用f.name访问属性即可消除报错:
filters.forEach(f => { if(typeof f.value === 'string'){ // 此时f.name被自动推导为IRow的字符串键,row[f.name]类型为string result = doesStringContainsKeyword(row[f.name], f.value) console.log({result}) } })
这种写法不仅能解决类型报错,还能在传参时自动校验name和value的类型匹配关系,从根源避免非法参数传入。
方案2:自定义类型守卫(兼容现有类型定义)
如果不想调整现有接口结构,可以写一个类型守卫函数,手动告诉TS类型对应关系:
type StringFieldNames = KeysOfType<IRow, string> function isStringFilter(f: IFiltersNumber | IFiltersString): f is IFiltersString & {name: StringFieldNames} { return typeof f.value === 'string' }
将判断分支替换为类型守卫调用即可:
filters.forEach(f => { if(isStringFilter(f)){ result = doesStringContainsKeyword(row[f.name], f.value) console.log({result}) } })
方案3:局部类型断言(快速临时修复)
就是你当前使用的row[name] as string写法,优点是改动最小,缺点是不会做任何类型校验,即使传入name为数字键、value为字符串的非法参数也不会报错,仅适合临时快速修复场景。
内容的提问来源于stack exchange,提问作者AmirA

