You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;
  };

问题原因

  1. TS无法从item[prop] && ...的逻辑中自动推断出item[prop]的类型已变为NonNullable<T[K]>,逻辑与操作的类型推断能力有限;
  2. 扩展对象{...item, [prop]: item[prop]}的类型被推断为T & { [x: string]: T[K] },索引签名[x: string]无法匹配具体属性K,导致与目标映射类型不兼容。

内容的提问来源于stack exchange,提问作者Lordbalmon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 00:37:11