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 | import type { App, Component } from 'vue'; | 1 | import type { App, Component } from 'vue'; |
2 | import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'; | 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 | import { unref } from 'vue'; | 5 | import { unref } from 'vue'; |
6 | import { isArray, isObject } from '/@/utils/is'; | 6 | import { isArray, isObject } from '/@/utils/is'; |
7 | 7 | ||
@@ -34,25 +34,50 @@ export function setObjToUrlParams(baseUrl: string, obj: any): string { | @@ -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 | export function deepMerge<T extends object | null | undefined, U extends object | null | undefined>( | 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 | ): T & U { | 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 | export function openWindow( | 83 | export function openWindow( |