Commit c516d392259c18b88f1d2edd6e89f10b8b48186c

Authored by Kirk Lin
Committed by GitHub
1 parent a1283c13

fix(deepMerge): the default merge strategy is to replace the array (#2843)

src/utils/__test__/index.test.ts 0 → 100644
  1 +// 暂时未安装依赖,无法测试
  2 +// @ts-ignore
  3 +import { describe, expect, test } from 'vitest';
  4 +import { deepMerge } from '@/utils';
  5 +
  6 +describe('deepMerge function', () => {
  7 + test('should merge two objects recursively', () => {
  8 + const source = {
  9 + a: { b: { c: 1 }, d: [1, 2] },
  10 + e: [1, 2],
  11 + foo: { bar: 3 },
  12 + array: [
  13 + {
  14 + does: 'work',
  15 + too: [1, 2, 3],
  16 + },
  17 + ],
  18 + };
  19 + const target = {
  20 + a: { b: { d: [3] } },
  21 + e: [3],
  22 + foo: { baz: 4 },
  23 + qu: 5,
  24 + array: [
  25 + {
  26 + does: 'work',
  27 + too: [4, 5, 6],
  28 + },
  29 + {
  30 + really: 'yes',
  31 + },
  32 + ],
  33 + };
  34 + const expected = {
  35 + a: { b: { c: 1, d: [3] }, d: [1, 2] },
  36 + e: [3],
  37 + foo: {
  38 + bar: 3,
  39 + baz: 4,
  40 + },
  41 + array: [
  42 + {
  43 + does: 'work',
  44 + too: [4, 5, 6],
  45 + },
  46 + {
  47 + really: 'yes',
  48 + },
  49 + ],
  50 + qu: 5,
  51 + };
  52 + expect(deepMerge(source, target)).toEqual(expected);
  53 + });
  54 +
  55 + test('should replace arrays by default', () => {
  56 + const source = {
  57 + a: { b: { d: [1, 2] } },
  58 + e: [1, 2],
  59 + };
  60 + const target = {
  61 + a: { b: { d: [3] } },
  62 + e: [3],
  63 + };
  64 + const expected = {
  65 + a: { b: { d: [3] } },
  66 + e: [3],
  67 + };
  68 + expect(deepMerge(source, target)).toEqual(expected);
  69 + });
  70 +
  71 + test("should union arrays using mergeArrays = 'union'", () => {
  72 + const source = {
  73 + a: { b: { d: [1, 2] } },
  74 + e: [1, 2],
  75 + };
  76 + const target = {
  77 + a: { b: { d: [2, 3] } },
  78 + e: [3],
  79 + };
  80 + const expected = {
  81 + a: { b: { d: [1, 2, 3] } },
  82 + e: [1, 2, 3],
  83 + };
  84 + expect(deepMerge(source, target, 'union')).toEqual(expected);
  85 + });
  86 +
  87 + test("should intersect arrays using mergeArrays = 'intersection'", () => {
  88 + const source = {
  89 + a: { b: { d: [1, 2] } },
  90 + e: [1, 2],
  91 + };
  92 + const target = {
  93 + a: { b: { d: [2, 3] } },
  94 + e: [3],
  95 + };
  96 + const expected = {
  97 + a: { b: { d: [2] } },
  98 + e: [],
  99 + };
  100 + expect(deepMerge(source, target, 'intersection')).toEqual(expected);
  101 + });
  102 +
  103 + test("should concatenate arrays using mergeArrays = 'concat'", () => {
  104 + const source = {
  105 + a: { b: { d: [1, 2] } },
  106 + e: [1, 2],
  107 + };
  108 + const target = {
  109 + a: { b: { d: [2, 3] } },
  110 + e: [3],
  111 + };
  112 + const expected = {
  113 + a: { b: { d: [1, 2, 2, 3] } },
  114 + e: [1, 2, 3],
  115 + };
  116 + expect(deepMerge(source, target, 'concat')).toEqual(expected);
  117 + });
  118 +});
... ...
src/utils/index.ts
1 1 import type { App, Component } from 'vue';
2 2 import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router';
3 3  
4   -import { cloneDeep, mergeWith, uniq } from 'lodash-es';
  4 +import { intersectionWith, isEqual, mergeWith, unionWith } from 'lodash-es';
5 5 import { unref } from 'vue';
6 6 import { isArray, isObject } from '/@/utils/is';
7 7  
... ... @@ -34,25 +34,50 @@ export function setObjToUrlParams(baseUrl: string, obj: any): string {
34 34 }
35 35  
36 36 /**
37   -
38   - 递归合并两个对象。
39   - Recursively merge two objects.
40   - @param target 目标对象,合并后结果存放于此。The target object to merge into.
41   - @param source 要合并的源对象。The source object to merge from.
42   - @returns 合并后的对象。The merged object.
  37 + * Recursively merge two objects.
  38 + * 递归合并两个对象。
  39 + *
  40 + * @param source The source object to merge from. 要合并的源对象。
  41 + * @param target The target object to merge into. 目标对象,合并后结果存放于此。
  42 + * @param mergeArrays How to merge arrays. Default is "replace".
  43 + * 如何合并数组。默认为replace。
  44 + * - "union": Union the arrays. 对数组执行并集操作。
  45 + * - "intersection": Intersect the arrays. 对数组执行交集操作。
  46 + * - "concat": Concatenate the arrays. 连接数组。
  47 + * - "replace": Replace the source array with the target array. 用目标数组替换源数组。
  48 + * @returns The merged object. 合并后的对象。
43 49 */
44 50 export function deepMerge<T extends object | null | undefined, U extends object | null | undefined>(
45   - target: T,
46   - source: U,
  51 + source: T,
  52 + target: U,
  53 + mergeArrays: 'union' | 'intersection' | 'concat' | 'replace' = 'replace',
47 54 ): T & U {
48   - return mergeWith(cloneDeep(target), source, (objValue, srcValue) => {
49   - if (isObject(objValue) && isObject(srcValue)) {
50   - return mergeWith(cloneDeep(objValue), srcValue, (prevValue, nextValue) => {
51   - // 如果是数组,合并数组(去重) If it is an array, merge the array (remove duplicates)
52   - return isArray(prevValue) ? uniq(prevValue, nextValue) : undefined;
53   - });
  55 + if (!target) {
  56 + return source as T & U;
  57 + }
  58 + if (!source) {
  59 + return target as T & U;
  60 + }
  61 + if (isArray(target) && isArray(source)) {
  62 + switch (mergeArrays) {
  63 + case 'union':
  64 + return unionWith(target, source, isEqual) as T & U;
  65 + case 'intersection':
  66 + return intersectionWith(target, source, isEqual) as T & U;
  67 + case 'concat':
  68 + return target.concat(source) as T & U;
  69 + case 'replace':
  70 + return source as T & U;
  71 + default:
  72 + throw new Error(`Unknown merge array strategy: ${mergeArrays}`);
54 73 }
55   - });
  74 + }
  75 + if (isObject(target) && isObject(source)) {
  76 + return mergeWith({}, target, source, (targetValue, sourceValue) => {
  77 + return deepMerge(targetValue, sourceValue, mergeArrays);
  78 + }) as T & U;
  79 + }
  80 + return source as T & U;
56 81 }
57 82  
58 83 export function openWindow(
... ...