npm.io
4.0.1 • Published 4 months ago

deepmerge-plus

Licence
MIT
Version
4.0.1
Deps
3
Size
431 kB
Vulns
0
Weekly
0

deepmerge-plus

深度合併兩個 JavaScript 物件的可列舉屬性 / Merge the enumerable attributes of two objects deeply.

pnpm add deepmerge-plus

概述

deepmerge-plus 是一個功能強大的 JavaScript 物件深度合併函式庫,支援:

  • � depth-first 深度合併(遞迴處理巢狀物件)
  • 陣列合併(可自訂合併策略)
  • 靈活的選項配置
  • 自訂合併邏輯

安裝

# 使用 pnpm(推薦)
pnpm add deepmerge-plus

# 使用 npm
npm install deepmerge-plus

# 使用 yarn
yarn add deepmerge-plus

快速開始

const merge = require('deepmerge-plus');

const target = {
  foo: { bar: 5 },
  array: [{ does: 'work', too: [1, 2, 3] }]
};

const source = {
  foo: { baz: 4 },
  quux: 5,
  array: [{ does: 'work', too: [4, 5, 6] }, { really: 'yes' }]
};

const result = merge(target, source);
console.log(result);
// => { foo: { bar: 5, baz: 4 }, array: [...], quux: 5 }

核心概念:左至右合併

merge(target, source, options) 中:

參數 術語 說明
target 左側(left) 被合併的物件,代表現有值
source 右側(right) 要合併進來的物件,代表新值
預設行為
  • 來源物件的值會覆蓋目標物件的值
  • 合併結果是將「右側」的值合併進「左側」
  • 合併會建立一個新物件,因此 targetsource 都不會被修改
const target = { name: 'Alice', age: 25 };
const source = { name: 'Bob', city: 'Taipei' };

const result = merge(target, source);
// => { name: 'Bob', age: 25, city: 'Taipei' }
// name 被 source 覆蓋,age 保留 target,city 來自 source

API

merge(target, source, [options])

深度合併兩個物件 targetsource,返回一個包含兩者元素的新合併物件。

const result = merge(objectA, objectB, options);
merge.all(arrayOfObjects, [options])

將任意數量的物件合併成單一結果物件。

const result = merge.all([obj1, obj2, obj3], options);

選項說明

1. clone

是否啟用深度複製功能。

  • 類型: boolean
  • 預設值: true
merge(target, source, { clone: true }); // 預設,深度複製物件
merge(target, source, { clone: false }); // 不複製,直接引用
2. arrayMerge

自訂陣列合併函式。

  • 類型: function(target, source, options): array
  • 預設行為: 串接兩個陣列
// 覆寫模式:來源陣列覆蓋目標陣列
function overwriteMerge(destinationArray, sourceArray) {
  return sourceArray;
}

merge([1, 2, 3], [3, 2, 1], { arrayMerge: overwriteMerge });
// => [3, 2, 1]
// 僅保留目標陣列
const keepTarget = (destination) => destination;
merge({ arr: [1,2,3] }, { arr: ['a','b','c'] }, { arrayMerge: keepTarget });
// => { arr: [1, 2, 3] }
3. isMergeableObject

自訂可合併物件判斷函式。

  • 類型: function(value): boolean
const moment = require('moment');

merge(target, source, {
  isMergeableObject(value) {
    if (moment.isMoment(value)) return false;
    return isMergeableObject(value);
  }
});
4. keyValueOrMode(已棄用)

使用 || 運算子邏輯,已由 keyValueUpsertMode 取代。

  • 類型: boolean
  • 狀態: 已棄用,不建議在新程式碼中使用
5. keyValueUpsertMode(推薦)

Upsert 模式,控制何時應該保留目標中的現有值。

  • 類型: booleanfunction
  • 預設值: undefined
true

保留目標中已存在的值(undefined 除外)。

const target = { name: 'Alice', age: undefined, count: 0, email: null };
const source = { name: 'Bob', age: 30, count: 5, email: 'bob@example.com' };

const result = merge(target, source, { keyValueUpsertMode: true });
// => { name: 'Alice', age: 30, count: 0, email: null }
// name: 保留 'Alice'(不是 undefined)
// age: 使用 30(目標值是 undefined)
// count: 保留 0(不是 undefined)
// email: 保留 null(不是 undefined)

關鍵特性

  • 使用 ?? 運算子,區分 undefined 與其他 falsy 值
  • null0false'' 都會被保留
  • 與 lodash 的 _.defaultsDeep 概念相似
function

自訂函式,根據條件決定是否保留目標的值。

const target = { name: 'Alice', count: 0 };
const source = { name: 'Bob', count: 5 };

const result = merge(target, source, {
  keyValueUpsertMode: (value, options, tmpRuntimeTarget) => {
    const targetValue = tmpRuntimeTarget.target?.[tmpRuntimeTarget.key];
    return targetValue === 0; // 只有當目標值為 0 時保留
  }
});
// => { name: 'Bob', count: 0 }
// name: targetValue='Alice' !== 0,回傳 false,使用 source 的 'Bob'
// count: targetValue=0 === 0,回傳 true,保留 target 的 0

函式參數說明

參數 類型 說明
value unknown 來源物件中該鍵的值
optionsRuntime IOptions 目前的合併選項
tmpRuntimeTarget ICache 包含 targetsourcedestinationkey 等資訊
tmpRuntimeData ITmpRuntimeData 包含 levelpathsrootparent 等資訊
進一步閱讀

如需了解 keyValueUpsertMode 與陣列合併策略的詳細組合應用,請參閱:


實際應用場景

1. 配置合併(保留使用者設定)
// 預設配置
const defaultConfig = {
  theme: 'light',
  language: 'en',
  notifications: { email: true, sms: false },
  timeout: 3000
};

// 使用者自訂配置
const userConfig = {
  theme: 'dark',
  notifications: { email: false }
};

// 合併:保留使用者設定,填充預設值
const finalConfig = merge(defaultConfig, userConfig, {
  keyValueUpsertMode: true
});
// => { theme: 'dark', language: 'en', notifications: { email: false }, timeout: 3000 }

對比 lodash

  • _.defaults(userConfig, defaultConfig) - 填充缺失的鍵
  • merge(defaultConfig, userConfig, { keyValueUpsertMode: true }) - 保留使用者設定
2. 表單資料合併
// 預設表單值
const defaultValues = {
  username: '',
  password: '',
  rememberMe: false,
  preferences: { newsletter: true, language: 'en' }
};

// 使用者已輸入的資料
const userInput = {
  username: 'alice',
  preferences: { language: 'zh-TW' }
};

// 合併:保留使用者已輸入的資料
const formData = merge(defaultValues, userInput, {
  keyValueUpsertMode: true
});
// => { username: 'alice', password: '', rememberMe: false, preferences: { newsletter: true, language: 'zh-TW' } }
3. 環境變數覆蓋
// 基礎環境配置
const baseConfig = {
  API_URL: 'https://api.example.com',
  DEBUG: false,
  CACHE: { enabled: true, ttl: 3600 }
};

// 開發環境配置
const devConfig = {
  DEBUG: true,
  API_URL: 'https://dev-api.example.com'
};

// 生產環境配置
const prodConfig = {
  DEBUG: false
};

// 合併環境配置(保留最基礎的值)
const envConfig = merge(baseConfig, devConfig, {
  keyValueUpsertMode: true
});
// => { API_URL: 'https://dev-api.example.com', DEBUG: true, CACHE: { enabled: true, ttl: 3600 } }
4. 深度合併(類似 _.merge)
const target = { user: { name: 'Alice', age: 25 }, role: 'admin' };
const source = { user: { name: 'Bob', city: 'Taipei' }, role: 'user' };

// 預設深度合併(source 覆蓋 target)
const result1 = merge(target, source);
// => { user: { name: 'Bob', age: 25, city: 'Taipei' }, role: 'user' }

// 使用 keyValueUpsertMode 保留 target 的值
const result2 = merge(target, source, { keyValueUpsertMode: true });
// => { user: { name: 'Alice', age: 25, city: 'Taipei' }, role: 'admin' }

與 lodash 函式對比

lodash 函式 deepmerge-plus 實作 說明
_.defaults merge(target, source, { keyValueUpsertMode: true }) 淺層預設值(需正確參數順序)
_.defaultsDeep merge(target, source, { keyValueUpsertMode: true }) 深度預設值
_.merge merge(target, source) 預設深度合併

重要語義差異

  • _.defaults / _.defaultsDeep:填充缺失的鍵(fill missing)
  • keyValueUpsertMode: true:有值則保留(preserve if exists)

類型定義

interface IOptions {
  /** 是否啟用深度複製,預設 true */
  clone?: boolean;

  /** 自訂陣列合併函式 */
  arrayMerge?: <T extends any[]>(target: T, source: any[], options?: IOptions) => T[];

  /** 自訂可合併物件判斷函式 */
  isMergeableObject?: (value: any) => boolean;

  /** 已棄用:使用 keyValueUpsertMode 取代 */
  keyValueOrMode?: boolean;

  /** Upsert 模式:true 或自訂函式 */
  keyValueUpsertMode?:
    | boolean
    | ((value: unknown, optionsRuntime?: IOptions, tmpRuntimeTarget?: ICache) => boolean);
}

測試

pnpm test

授權

MIT

Keywords