Commit c516d392259c18b88f1d2edd6e89f10b8b48186c
Committed by
GitHub
1 parent
a1283c13
fix(deepMerge): the default merge strategy is to replace the array (#2843)
Showing
2 changed files
with
159 additions
and
16 deletions
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( | ... | ... |