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 39ddac39..c0db65d5 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 = (current: any, next: 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 mergeWith( + 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,11 @@ export function merge(...sources: T[]) { return clone; } + +/** + * Merge multiple objects into a new single object. + * Arrays will be replaced by default. + */ +export function merge(...sources: T[]) { + return mergeWith(sources); +} diff --git a/tests/utils.test.ts b/tests/utils.test.ts index dc0f439d..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, { merge } from '../src/utils/set'; +import set, { mergeWith, merge } from '../src/utils/set'; describe('utils', () => { it('get', () => { @@ -252,6 +252,39 @@ describe('utils', () => { [symbol]: 1, }); }); + + it('customMerge for custom logic', () => { + const src = { + rest: 233, + list: [ + { + a: 1, + }, + ], + }; + const tgt = { + list: [ + { + b: 1, + }, + ], + }; + + const merged = mergeWith([src, tgt], { + prepareArray: current => { + return [...(current || [])]; + }, + }); + expect(merged).toEqual({ + rest: 233, + list: [ + { + a: 1, + b: 1, + }, + ], + }); + }); }); });