如何在TypeScript中实现移除含空值指定属性项的通用Reducer?
TypeScript 通用数组Reducer类型收窄问题
需求
要创建一个通用Reducer,处理数组T[]时实现以下功能:
- 指定目标属性名
K(K extends keyof T) - 过滤掉该属性为
null/undefined的项,同时将结果数组中每个元素的该属性类型收窄为NonNullable<T[K]>,最终得到类型为{ [P in keyof T]: P extends K ? NonNullable<T[P]> : T[P] }[]的数组
错误代码及问题
以下Reducer未能正确完成类型收窄:
export const reduceRemoveIfPropertyEmpty = <T extends Object, K extends keyof T>(prop: K) => ( agg: { [P in keyof T]: P extends K ? NonNullable<T[P]> : T[P] }[], item: T ) => { item[prop] && agg.push({ ...item, [prop]: item[prop] }) return agg }
在agg.push(...)处触发TS错误:
| TS错误信息 |
|---|
| Argument of type 'T & { [x: string]: T[K]; }' is not assignable to parameter of type '{ [P in keyof T]: P extends K ? NonNullable<T[P]> : T[P]; }'.ts(2345) |
| (parameter) prop: K extends keyof T |
注意:tsconfig已启用
"strict"和"strictNullChecks"模式
解决方法
方案1:类型断言直接指定目标类型
通过手动断言告诉TS扩展后的对象符合目标类型:
export const reduceRemoveIfPropertyEmpty = <T extends object, K extends keyof T>(prop: K) => ( agg: { [P in keyof T]: P extends K ? NonNullable<T[P]> : T[P] }[], item: T ) => { const propValue = item[prop]; // 用 != null 同时排除null和undefined if (propValue != null) { agg.push({ ...item, [prop]: propValue } as { [P in keyof T]: P extends K ? NonNullable<T[P]> : T[P] }); } return agg; };
方案2:自定义类型守卫(更安全)
先写一个类型守卫函数,帮助TS识别非空属性的对象:
// 类型守卫:判断对象的指定属性非空,并收窄类型 function hasNonNullProp<T, K extends keyof T>(obj: T, prop: K): obj is T & { [P in K]: NonNullable<T[P]> } { return obj[prop] != null; } export const reduceRemoveIfPropertyEmpty = <T extends object, K extends keyof T>(prop: K) => ( agg: { [P in keyof T]: P extends K ? NonNullable<T[P]> : T[P] }[], item: T ) => { if (hasNonNullProp(item, prop)) { // 此时item的类型已被收窄,可直接push agg.push(item); } return agg; };
问题原因
- TS无法从
item[prop] && ...的逻辑中自动推断出item[prop]的类型已变为NonNullable<T[K]>,逻辑与操作的类型推断能力有限; - 扩展对象
{...item, [prop]: item[prop]}的类型被推断为T & { [x: string]: T[K] },索引签名[x: string]无法匹配具体属性K,导致与目标映射类型不兼容。
内容的提问来源于stack exchange,提问作者Lordbalmon
相关产品推荐
相关产品推荐

