From cac988e89f8049ba18017a7c4d95aefcb8b61158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 8 Dec 2025 15:50:19 +0800 Subject: [PATCH 1/6] feat: support deep merge --- src/utils/set.ts | 28 +++++++++++++++++++++++++--- tests/utils.test.ts | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/utils/set.ts b/src/utils/set.ts index 39ddac39..2474151c 100644 --- a/src/utils/set.ts +++ b/src/utils/set.ts @@ -66,10 +66,25 @@ function createEmpty(source: T) { const keys = typeof Reflect === 'undefined' ? Object.keys : Reflect.ownKeys; +// ================================ Merge ================================ +export type MergeFn = (srcVal: any, tgtVal: any) => any; + /** - * Merge objects which will create + * Merge multiple objects. Support custom merge logic. + * @param sources object sources + * @param config.prepareArray Customize array prepare function. + * It will return empty [] by default. + * So when match array, it will auto be override with next array in sources. */ -export function merge(...sources: T[]) { +export function deepMerge( + sources: T[], + config: { + prepareArray?: MergeFn; + } = {}, +) { + const { prepareArray } = config; + const finalPrepareArray: MergeFn = prepareArray || (() => []); + let clone = createEmpty(sources[0]); sources.forEach(src => { @@ -89,7 +104,7 @@ export function merge(...sources: T[]) { if (isArr) { // Array will always be override - clone = set(clone, path, []); + clone = set(clone, path, finalPrepareArray(originValue, value)); } else if (!originValue || typeof originValue !== 'object') { // Init container if not exist clone = set(clone, path, createEmpty(value)); @@ -109,3 +124,10 @@ export function merge(...sources: T[]) { return clone; } + +/** + * Merge objects which will create + */ +export function merge(...sources: T[]) { + return deepMerge(sources); +} diff --git a/tests/utils.test.ts b/tests/utils.test.ts index dc0f439d..6c2cd8a8 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,6 +1,6 @@ import pickAttrs from '../src/pickAttrs'; import get from '../src/utils/get'; -import set, { merge } from '../src/utils/set'; +import set, { deepMerge, merge } from '../src/utils/set'; describe('utils', () => { it('get', () => { @@ -252,6 +252,39 @@ describe('utils', () => { [symbol]: 1, }); }); + + it('deepMerge for custom logic', () => { + const src = { + rest: 233, + list: [ + { + a: 1, + }, + ], + }; + const tgt = { + list: [ + { + b: 1, + }, + ], + }; + + const merged = deepMerge([src, tgt], { + prepareArray: srcVal => { + return [...(srcVal || [])]; + }, + }); + expect(merged).toEqual({ + rest: 233, + list: [ + { + a: 1, + b: 1, + }, + ], + }); + }); }); }); From 67bfef83a48d2668bf3219208b87fe6a0a652f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 8 Dec 2025 16:02:31 +0800 Subject: [PATCH 2/6] chore: adjust --- src/utils/set.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/set.ts b/src/utils/set.ts index 2474151c..2e528466 100644 --- a/src/utils/set.ts +++ b/src/utils/set.ts @@ -67,7 +67,7 @@ function createEmpty(source: T) { const keys = typeof Reflect === 'undefined' ? Object.keys : Reflect.ownKeys; // ================================ Merge ================================ -export type MergeFn = (srcVal: any, tgtVal: any) => any; +export type MergeFn = (current: any, next: any) => any; /** * Merge multiple objects. Support custom merge logic. From 5e968facccdd656c04105fe64bfa818ead4bcb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 8 Dec 2025 16:36:23 +0800 Subject: [PATCH 3/6] chore: adjust --- tests/utils.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 6c2cd8a8..2c1680b4 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -271,8 +271,8 @@ describe('utils', () => { }; const merged = deepMerge([src, tgt], { - prepareArray: srcVal => { - return [...(srcVal || [])]; + prepareArray: current => { + return [...(current || [])]; }, }); expect(merged).toEqual({ From ce49d3279432079c254406f11f5e3e87ee71da63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 8 Dec 2025 16:40:41 +0800 Subject: [PATCH 4/6] chore: adjust --- src/utils/set.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/set.ts b/src/utils/set.ts index 2e528466..fcbe58ea 100644 --- a/src/utils/set.ts +++ b/src/utils/set.ts @@ -126,7 +126,8 @@ export function deepMerge( } /** - * Merge objects which will create + * Merge multiple objects into a new single object. + * Arrays will be replaced by default. */ export function merge(...sources: T[]) { return deepMerge(sources); From eece4e28f58d70ccd11ed93191c9703e526588da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 8 Dec 2025 16:49:53 +0800 Subject: [PATCH 5/6] chore: rename --- src/utils/set.ts | 4 ++-- tests/utils.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/set.ts b/src/utils/set.ts index fcbe58ea..b38fb13c 100644 --- a/src/utils/set.ts +++ b/src/utils/set.ts @@ -76,7 +76,7 @@ export type MergeFn = (current: any, next: any) => any; * It will return empty [] by default. * So when match array, it will auto be override with next array in sources. */ -export function deepMerge( +export function customMerge( sources: T[], config: { prepareArray?: MergeFn; @@ -130,5 +130,5 @@ export function deepMerge( * Arrays will be replaced by default. */ export function merge(...sources: T[]) { - return deepMerge(sources); + return customMerge(sources); } diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 2c1680b4..906c53f0 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,6 +1,6 @@ import pickAttrs from '../src/pickAttrs'; import get from '../src/utils/get'; -import set, { deepMerge, merge } from '../src/utils/set'; +import set, { customMerge, merge } from '../src/utils/set'; describe('utils', () => { it('get', () => { @@ -253,7 +253,7 @@ describe('utils', () => { }); }); - it('deepMerge for custom logic', () => { + it('customMerge for custom logic', () => { const src = { rest: 233, list: [ @@ -270,7 +270,7 @@ describe('utils', () => { ], }; - const merged = deepMerge([src, tgt], { + const merged = customMerge([src, tgt], { prepareArray: current => { return [...(current || [])]; }, From 5c8481ba927d2976d15e6604a744f26f9c73a441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 8 Dec 2025 16:55:29 +0800 Subject: [PATCH 6/6] chore: rename --- src/index.ts | 2 +- src/utils/set.ts | 4 ++-- tests/utils.test.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 84097aa4..166d66d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ export { default as useMergedState } from './hooks/useMergedState'; export { default as useControlledState } from './hooks/useControlledState'; export { supportNodeRef, supportRef, useComposeRef } from './ref'; export { default as get } from './utils/get'; -export { default as set, merge } from './utils/set'; +export { default as set, merge, mergeWith } from './utils/set'; export { default as warning, noteOnce } from './warning'; export { default as omit } from './omit'; export { default as toArray } from './Children/toArray'; diff --git a/src/utils/set.ts b/src/utils/set.ts index b38fb13c..c0db65d5 100644 --- a/src/utils/set.ts +++ b/src/utils/set.ts @@ -76,7 +76,7 @@ export type MergeFn = (current: any, next: any) => any; * It will return empty [] by default. * So when match array, it will auto be override with next array in sources. */ -export function customMerge( +export function mergeWith( sources: T[], config: { prepareArray?: MergeFn; @@ -130,5 +130,5 @@ export function customMerge( * Arrays will be replaced by default. */ export function merge(...sources: T[]) { - return customMerge(sources); + return mergeWith(sources); } diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 906c53f0..580c303c 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,6 +1,6 @@ import pickAttrs from '../src/pickAttrs'; import get from '../src/utils/get'; -import set, { customMerge, merge } from '../src/utils/set'; +import set, { mergeWith, merge } from '../src/utils/set'; describe('utils', () => { it('get', () => { @@ -270,7 +270,7 @@ describe('utils', () => { ], }; - const merged = customMerge([src, tgt], { + const merged = mergeWith([src, tgt], { prepareArray: current => { return [...(current || [])]; },