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

TypeScript中能否为映射类型每个属性实现独立泛型推断

normalizeData函数独立reducer类型推断实现方案

问题原因

原有类型定义为所有reducer配置项共用同一个InitialValue泛型,TypeScript会以首个配置项的initialValue类型为统一基准,要求其余配置项的累加器类型完全匹配,导致不同reducer需要独立累加器类型时触发类型报错。

实现思路

不需要为每个属性单独声明泛型参数,利用TypeScript对泛型对象的逐属性推断能力即可实现需求:

  • 将reducers配置整体作为一个泛型参数约束,让TS自动保留每个配置项的独立类型
  • 通过映射类型遍历配置对象,将每个配置项的initialValue类型绑定到同项reduce回调的acc参数类型
  • 同样通过映射类型提取最终返回值的结构类型,保证返回值类型和运行结果一致

修正后完整代码

// 数组reduce回调基础类型
type ReduceCallback<Value, Output> = (
  previousValue: Output,
  currentValue: Value,
  currentIndex: number,
  array: Value[],
) => Output

// 示例数据类型
type Person = {
  id: string
  name: string
  parentId?: string
  age: number
}

// 修正类型后的normalizeData函数
export function normalizeData<
  Model,
  Reducers extends Record<string, { initialValue: any; reduce?: Function }>
>(
  data: Model[],
  reducers: {
    [K in keyof Reducers]: {
      initialValue: Reducers[K]['initialValue']
      // 每个reducer的acc类型自动绑定同项initialValue类型
      reduce?: ReduceCallback<Model, Reducers[K]['initialValue']>
    }
  }
): { [K in keyof Reducers]: Reducers[K]['initialValue'] } {
  const reducerKeys = Object.keys(reducers) as Array<keyof Reducers>

  // 初始化返回值
  const initialValues = reducerKeys.reduce(
    (obj, key) => ({
      ...obj,
      [key]: reducers[key].initialValue,
    }),
    {} as { [K in keyof Reducers]: Reducers[K]['initialValue'] },
  )

  const reduceCallbacks = reducerKeys.map((key) => ({ key, callback: reducers[key].reduce }))

  // 遍历数据执行所有reducer
  const normalizedData = data.reduce((acc, datum, index, array) => {
    return reduceCallbacks.reduce((acc, { key, callback }) => {
      const callbackWithDefault = callback || ((id: any) => id)
      return {
        ...acc,
        [key]: callbackWithDefault(acc[key], datum, index, array),
      }
    }, acc)
  }, initialValues)

  return normalizedData
}

// 测试用例 类型完全匹配
const parent: Person = {
  id: "001",
  name: "Dad",
  parentId: undefined,
  age: 53,
}
const son: Person = {
  id: "002",
  name: "Son",
  parentId: "001",
  age: 12,
}

const result = normalizeData([parent, son], {
  byId: {
    initialValue: {} as { [key: string]: Person },
    // acc自动推断为{[key: string]: Person}类型
    reduce: (acc, person) => {
      acc[person.id] = person
      return acc
    },
  },
  list: {
    initialValue: [] as Person[],
    // acc自动推断为Person[]类型
    reduce: (acc, person) => {
      acc.push(person)
      return acc
    },
  },
})

// result自动推断类型为:
// {
//   byId: { [key: string]: Person },
//   list: Person[]
// }

类型效果

  • 每个reducer配置项的reduce回调acc参数会自动匹配同项initialValue的类型,无需手动标注
  • 函数返回值自动推断为和reducer配置对应的结构化类型,无需额外声明
  • 支持任意数量、任意累加器类型的reducer配置项,不会出现类型互相干扰的问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 12:12:21