UNPKG

deepmerge-plus

Version:

用於深度(遞迴)合併 JavaScript 物件的函式庫 / A library for deep (recursive) merging of JavaScript objects

342 lines (270 loc) 8.49 kB
# keyValueUpsertMode 與陣列合併策略 # keyValueUpsertMode with Array Merge Strategies ## 概述 本文件說明 `keyValueUpsertMode` 選項與不同陣列合併策略的組合使用方式,並提供實用場景範例。 This document explains how to combine `keyValueUpsertMode` option with different array merge strategies, with practical examples. --- ## 陣列合併策略 deepmerge 支援多種陣列合併策略,透過 `arrayMerge` 選項自訂。 deepmerge supports multiple array merge strategies through the `arrayMerge` option. ### 策略比較表 | 策略 | 函式 | 行為 | 適用情境 | |------|------|------|---------| | **預設串接** | (不指定) | `[...target, ...source]` | 兩者合併,保留所有元素 | | **左邊為主** | `arrayMergeTargetWins` | `target` | 保留預設值,忽略新資料 | | **右邊為主** | `arrayMergeSourceWins` | `source` | 完全覆蓋,使用新資料 | ### 策略程式碼 ```typescript /** * 預設串接:合併兩邊陣列 * Default concat: Merge both arrays */ function defaultArrayMerge(target: any[], source: any[]): any[] { return target.concat(source); } /** * 左邊為主:完全保留目標陣列 * Left primary: Keep target array completely */ function arrayMergeTargetWins(target: any[], source: any[]): any[] { return target; } /** * 右邊為主:完全使用來源陣列 * Right primary: Use source array completely */ function arrayMergeSourceWins(target: any[], source: any[]): any[] { return source; } ``` --- ## 與 keyValueUpsertMode 搭配 ### keyValueUpsertMode: true 的作用 `keyValueUpsertMode: true` 時: - 如果目標值不是 `undefined`,保留目標值 - 如果目標值是 `undefined`,使用來源值 - 對於陣列,這會影響整個陣列是否被採用 When `keyValueUpsertMode: true`: - If target value is not `undefined`, keep target value - If target value is `undefined`, use source value - For arrays, this affects whether the entire array is used ### 三種策略對比 ```typescript const target = { tags: ['a', 'b', 'c'], name: 'Alice', }; const source = { tags: ['x', 'y', 'z'], name: 'Bob', }; // 三種策略結果 const resultTargetWins = merge(target, source, { keyValueUpsertMode: true, arrayMerge: arrayMergeTargetWins, }); // tags = ['a', 'b', 'c'] (左邊為主-目標) // name = 'Alice' (keyValueUpsertMode 保留) const resultSourceWins = merge(target, source, { keyValueUpsertMode: true, arrayMerge: arrayMergeSourceWins, }); // tags = ['x', 'y', 'z'] (右邊為主-來源) // name = 'Alice' (keyValueUpsertMode 保留) const resultConcat = merge(target, source, { keyValueUpsertMode: true, }); // tags = ['a', 'b', 'c', 'x', 'y', 'z'] (預設串接) // name = 'Alice' (keyValueUpsertMode 保留) ``` ### 結果對照表 | 策略 | `tags` 結果 | `name` 結果 | |------|-------------|-------------| | `arrayMergeTargetWins` | `['a', 'b', 'c']` | `'Alice'` | | `arrayMergeSourceWins` | `['x', 'y', 'z']` | `'Alice'` | | 預設串接 | `['a', 'b', 'c', 'x', 'y', 'z']` | `'Alice'` | --- ## 實用場景 ### 場景 1:使用者配置與預設配置合併 ```typescript /** * 情境:合併使用者配置與預設配置 * - 使用者已設定的值應該被保留 * - 新的設定應該被添加 * - 陣列(如外掛清單)應該完全替換而非合併 */ const defaultConfig = { plugins: ['analytics', 'seo', 'security'], theme: 'light', language: 'en', }; const userConfig = { plugins: ['custom-plugin'], theme: 'dark', }; // 使用 arrayMergeSourceWins:使用者的外掛清單完全替換預設清單 const result = merge(defaultConfig, userConfig, { keyValueUpsertMode: true, arrayMerge: arrayMergeSourceWins, }); // 結果: // { // plugins: ['custom-plugin'], // 完全替換 // theme: 'light', // 保留預設(keyValueUpsertMode) // language: 'en' // 新增鍵 // } ``` ### 場景 2:API 回應與快取合併 ```typescript /** * 情境:將 API 回應與本地快取合併 * - 已經存在的資料應該被保留(以快取為準) * - 新資料應該被添加 * - 歷史記錄陣列應該完全替換(只看最新) */ const cache = { history: ['action-1', 'action-2', 'action-3'], profile: { name: 'Cached User', updatedAt: '2024-01-01', }, }; const apiResponse = { history: ['action-4'], profile: { name: 'Cached User', updatedAt: '2024-01-15', }, }; const result = merge(cache, apiResponse, { keyValueUpsertMode: true, arrayMerge: arrayMergeSourceWins, }); // 結果: // { // history: ['action-4'], // 完全替換(只看最新) // profile: { // name: 'Cached User', // 保留快取值(keyValueUpsertMode) // updatedAt: '2024-01-01', // 保留舊時間(keyValueUpsertMode) // }, // } ``` ### 場景 3:表單預設值填充 ```typescript /** * 情境:用預設值填充表單 * - 使用者已填寫的值應該被保留 * - 未填寫的欄位使用預設值 * - 選項陣列應該完全替換(多選題) */ const defaultValues = { interests: ['reading', 'music', 'sports'], notifications: ['email', 'sms'], name: undefined, }; const userInput = { interests: ['coding'], notifications: ['push'], name: 'John', }; const result = merge(defaultValues, userInput, { keyValueUpsertMode: true, arrayMerge: arrayMergeSourceWins, }); // 結果: // { // interests: ['coding'], // 完全替換 // notifications: ['push'], // 完全替換 // name: 'John' // 使用使用者輸入(因為預設是 undefined) // } ``` ### 場景 4:預設值優先 ```typescript /** * 情境:系統預設值應該優先於使用者輸入 * - 某些關鍵設定不允許使用者修改 * - 使用 arrayMergeTargetWins 確保預設值不被覆蓋 */ const systemDefaults = { allowedPlugins: ['official-plugin'], maxUploadSize: 100, features: ['feature-a', 'feature-b'], }; const userSettings = { allowedPlugins: ['hacked-plugin'], // 嘗試添加惡意插件 maxUploadSize: 10000, // 嘗試增加上傳限制 features: ['feature-c'], // 嘗試添加功能 }; // 使用 arrayMergeTargetWins:系統預設值優先 const result = merge(systemDefaults, userSettings, { keyValueUpsertMode: true, arrayMerge: arrayMergeTargetWins, }); // 結果: // { // allowedPlugins: ['official-plugin'], // 保持預設 // maxUploadSize: 100, // 保持預設 // features: ['feature-a', 'feature-b'], // 保持預設 // } ``` --- ## 巢狀物件中的陣列行為 所有策略都會遞迴應用於巢狀物件中的陣列。 All strategies are recursively applied to arrays in nested objects. ```typescript const target = { config: { plugins: ['plugin-a', 'plugin-b'], modules: ['mod-1', 'mod-2'], }, }; const source = { config: { plugins: ['plugin-c'], modules: ['mod-3'], }, }; const result = merge(target, source, { keyValueUpsertMode: true, arrayMerge: arrayMergeSourceWins, }); // 巢狀陣列也會被替換: // { // config: { // plugins: ['plugin-c'], // 完全替換 // modules: ['mod-3'], // 完全替換 // }, // } ``` --- ## 注意事項 ### 1. keyValueUpsertMode 與陣列的互動 當目標陣列為 `undefined` 時: - `keyValueUpsertMode: true` 會使用來源陣列 - 之後再由 `arrayMerge` 策略決定合併方式 When target array is `undefined`: - `keyValueUpsertMode: true` will use source array - Then `arrayMerge` strategy decides how to merge ### 2. 空陣列的處理 | 情境 | `arrayMergeNoConcat` | `arrayMergeTargetWins` | 預設串接 | |------|---------------------|----------------------|---------| | target=`[]`, source=`['a']` | `['a']` | `[]` | `['a']` | | target=`['a']`, source=`[]` | `[]` | `['a']` | `['a']` | | target=`[]`, source=`[]` | `[]` | `[]` | `[]` | ### 3. 效能考量 - `arrayMergeTargetWins` `arrayMergeSourceWins` 最快(直接回傳) - `arrayMergeNoConcat` 次之(需複製來源陣列) - 預設串接最慢(需合併兩個陣列) --- ## 相關檔案 - `test/options/key-value-upsert-mode-no-array-merge.test.ts` - 測試檔案 - `src/index.ts` - 核心實作 - `docs/key-value-upsert-mode-analysis.md` - keyValueUpsertMode 分析文件 --- ## 更新日誌 | 日期 | 更新內容 | |------|---------| | 2026-03-26 | 新增 keyValueUpsertMode 與陣列合併策略文件 |