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

如何为mergeDeep函数实现类型层面的深度合并返回类型推断?

为递归合并函数mergeDeep实现准确的TypeScript类型推断

我有一个可递归合并对象与数组的JavaScript函数mergeDeep,功能逻辑符合预期,但在TypeScript环境下无法准确推断合并后的返回类型。需要优化该函数的类型定义,让返回类型能正确体现目标对象与所有源对象的类型组合。

原函数实现如下:

function mergeDeep(target, ...sources) {
  if (!sources.length) return target;

  const source = sources.shift();

  for (const key in source) {
    if (!Object.prototype.hasOwnProperty.call(source, key)) continue;

    const targetValue = target[key];
    const sourceValue = source[key];

    if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
      target[key] = targetValue.concat(sourceValue);
    } else if (isObject(targetValue) && isObject(sourceValue)) {
      target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue);
    } else {
      target[key] = source[key];
    }
  }

  return mergeDeep(target, ...sources);
}

function isObject(obj) {
  return obj && typeof obj === 'object' && !Array.isArray(obj);
}

我的目标是让mergeDeep在不同合并场景下都能正确推断返回类型,例如:

// 返回类型应为 { 0: number, 1: number, 2: number, numbers: number[] }
mergeDeep({ numbers: [1, 2, 3]}, [4, 5, 6])

// 返回类型应为 { strings: string[] }
mergeDeep({ strings: ['a', 'b', 'c']}, { strings: ['d', 'e', 'f']})

// 返回类型应为 { foo: (string | number)[] }
mergeDeep({ foo: ['bar']}, { foo: [55]})

// 返回类型应为 { a: number, b: number }
mergeDeep({ a: 1 }, { b: 2 })

// 返回类型应为 { a: number }
mergeDeep({ a: 1 }, { a: 2 })

// 返回类型应为 { a: { b: number, c: number } }
mergeDeep({ a: { b: 1 } }, { a: { c: 2 } })

我尝试过多种类型定义修改,但始终无法完全满足所有场景的类型推断需求,需要一个能覆盖上述所有情况的类型解决方案。


解决方案

要实现递归的类型合并,需要定义递归的类型工具处理对象、数组以及基本类型的合并逻辑,同时结合函数泛型定义推断返回类型。完整实现如下:

// 判断是否为数组类型
type IsArray<T> = T extends Array<any> ? true : false;

// 判断是否为对象类型(非数组)
type IsObject<T> = T extends object ? IsArray<T> extends false ? true : false : false;

// 递归合并两个类型
type MergeDeep<T, U> = 
  IsObject<T> extends true && IsObject<U> extends true 
    ? {
        [K in keyof T | keyof U]: 
          K extends keyof T & keyof U 
            ? MergeDeep<T[K], U[K]> 
            : K extends keyof T 
              ? T[K] 
              : U[K]
      }
    : IsArray<T> extends true && IsArray<U> extends true
      ? [...T, ...U]
      : U extends undefined
        ? T
        : U;

// 合并多个类型的递归工具
type MergeDeepMany<T extends any[]> = 
  T extends [infer First, ...infer Rest]
    ? MergeDeep<First, MergeDeepMany<Rest>>
    : {};

// 类型守卫:判断是否为对象(非数组)
function isObject(obj: unknown): obj is Record<string, unknown> {
  return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
}

// 带类型推断的mergeDeep函数
function mergeDeep<T extends object, U extends object[]>(target: T, ...sources: U): MergeDeepMany<[T, ...U]>;
function mergeDeep(target: any, ...sources: any[]): any {
  if (!sources.length) return target;

  const source = sources.shift();

  for (const key in source) {
    if (!Object.prototype.hasOwnProperty.call(source, key)) continue;

    const targetValue = target[key];
    const sourceValue = source[key];

    if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
      target[key] = targetValue.concat(sourceValue);
    } else if (isObject(targetValue) && isObject(sourceValue)) {
      target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue);
    } else {
      target[key] = source[key];
    }
  }

  return mergeDeep(target, ...sources);
}

验证场景

现在测试之前的例子,类型推断均符合预期:

  1. 对象合并数组:
const result1 = mergeDeep({ numbers: [1, 2, 3]}, [4, 5, 6]);
// 类型推断为:{ 0: number; 1: number; 2: number; numbers: number[]; }
  1. 同key数组合并:
const result2 = mergeDeep({ strings: ['a', 'b', 'c']}, { strings: ['d', 'e', 'f']});
// 类型推断为:{ strings: string[]; }
  1. 不同元素类型数组合并:
const result3 = mergeDeep({ foo: ['bar']}, { foo: [55]});
// 类型推断为:{ foo: (string | number)[]; }
  1. 对象新增属性:
const result4 = mergeDeep({ a: 1 }, { b: 2 });
// 类型推断为:{ a: number; b: number; }
  1. 对象属性覆盖:
const result5 = mergeDeep({ a: 1 }, { a: 2 });
// 类型推断为:{ a: number; }
  1. 嵌套对象合并:
const result6 = mergeDeep({ a: { b: 1 } }, { a: { c: 2 } });
// 类型推断为:{ a: { b: number; c: number; }; }

说明

  • MergeDeep类型负责处理两个类型的递归合并:如果都是对象则合并属性,如果都是数组则拼接数组类型,否则用源类型覆盖目标类型。
  • MergeDeepMany类型负责将多个源类型依次递归合并到目标类型上。
  • 函数重载确保传入参数为对象/数组类型,同时利用泛型推断返回MergeDeepMany处理后的类型。
  • 保留原函数逻辑,仅补充类型定义和类型守卫,保证功能不变的同时获得准确的类型推断。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:56:32