deepmerge-plus
Version:
用於深度(遞迴)合併 JavaScript 物件的函式庫 / A library for deep (recursive) merging of JavaScript objects
242 lines (174 loc) • 6.06 kB
Markdown
# ITmpRuntimeData 技術文件
# ITmpRuntimeData Technical Documentation
## 概述
`ITmpRuntimeData` 是 deepmerge 庫中的一個內部執行時資料介面,用於在遞迴合併過程中追蹤上下文資訊。它記錄了當前合併操作的位置、路徑和相關物件引用。
`ITmpRuntimeData` is an internal runtime data interface in the deepmerge library, used to track context information during recursive merge operations. It records the current merge operation's position, path, and related object references.
## 類型定義
### 核心介面
```typescript
export interface ITmpRuntimeData<
T extends IAnyRecord = IAnyRecord,
R extends IAnyRecord = IAnyRecord
>
{
/** 遞迴深度計數(根為 0)/ Recursion depth count (root is 0) */
readonly level: number;
/** 從根到目前位置的路徑陣列 / Path array from root to current position */
readonly paths: (keyof R | keyof T | ITSPropertyKey | number)[];
/** 最頂層的 destination 物件(永遠不變)/ Top-level destination object (always unchanged) */
readonly root: R;
/** 目前層級的父物件 / Parent object at current level */
readonly parent: T;
/** 目前正在處理的鍵名 / Key currently being processed */
readonly key: keyof T;
}
```
### 相關介面:ICache
```typescript
export interface ICache
{
/** 鍵名 / Key name */
key?
/** 來源物件 / Source object */
source?
/** 目標物件 / Target object */
target?
/** 目的物件 / Destination object */
destination?
}
```
## 核心概念
### 1. 遞迴深度追蹤 (Level Tracking)
每次呼叫 `deepmerge` 遞迴時,`level` 會遞增:
```
根層級: level = 0
第一層: level = 1
第二層: level = 2
...以此類推
```
### 2. 路徑陣列 (Paths Array)
`paths` 陣列記錄了從根物件到目前位置的完整路徑:
```
合併 { a: { b: { c: 1 } } } 時:
- 根層級: paths = []
- a: paths = ['a']
- b: paths = ['a', 'b']
- c: paths = ['a', 'b', 'c']
```
### 3. Root 永遠不變
`root` 指向最開始建立的 destination 物件,在整個遞迴過程中保持不變。這使得我們可以:
- 使用 `_.get(root, paths)` 取得目前位置的物件
- 在任何層級都能取得最頂層的資料
### 4. Key/Parent 關係
- `parent`: 目前層級的父物件
- `key`: 目前正在處理的鍵名
- 可透過 `parent[key]` 取得目前的值
## 遞迴流程範例
### 範例:合併巢狀物件
```typescript
const target = {
level1: {
level2: {
value: 'original'
}
}
};
const source = {
level1: {
level2: {
value: 'updated',
extra: 'data'
}
}
};
merge(target, source);
```
### 執行時序
| 階段 | level | paths | root | parent | key |
|------|-------|-------|------|--------|-----|
| 初始 | 0 | [] | {} | {} | undefined |
| level1 | 1 | ['level1'] | {} | {} | 'level1' |
| level2 | 2 | ['level1', 'level2'] | {} | {level1:{}} | 'level2' |
| value | 3 | ['level1', 'level2', 'value'] | {} | {level1:{level2:{}}} | 'value' |
## ITmpRuntimeData 與 ICache 的差異
| 特性 | ITmpRuntimeData | ICache |
|------|-----------------|--------|
| 主要用途 | 追蹤遞迴路徑和深度 | 維護物件引用關係 |
| 關注點 | **在哪裡** (位置) | **是什麼** (物件) |
| 關鍵屬性 | level, paths, root | source, target, destination |
| 變化頻率 | 每次遞迴都會變化 | 每次處理鍵值時會變化 |
## 使用場景
### 1. 自訂 isMergeableObject
在自訂合併邏輯中,可以利用 `tmpRuntimeData` 取得目前位置資訊:
```typescript
const options = {
isMergeableObject: (value, defaultCheck, options, tmpRuntimeTarget, tmpRuntimeData) => {
// 根據目前路徑決定是否可合併
if (tmpRuntimeData?.paths.includes('protected')) {
return false; // protected 欄位不可合併
}
return defaultCheck(value);
}
};
```
### 2. 自訂 arrayMerge
在自訂陣列合併策略中,可以使用 `tmpRuntimeData` 取得上下文:
```typescript
const options = {
arrayMerge: (target, source, options, tmpRuntimeData) => {
console.log(`目前 level: ${tmpRuntimeData.level}`);
console.log(`目前路徑: ${tmpRuntimeData.paths.join('.')}`);
// 自訂合併邏輯...
return [...target, ...source];
}
};
```
## 測試驗證要點
根據 `test\tmp-runtime-data.test.ts`,以下 invariant 必須維持:
### 1. level 等於 paths.length
```typescript
expect(tmpRuntimeData.level).toBe(tmpRuntimeData.paths.length);
```
### 2. root 是最頂層物件
```typescript
// root 在所有巢狀層級中保持不變
expect(data.root).toBe(rootObject);
```
### 3. 可使用 _.get(root, paths) 取得值
```typescript
const value = _.get(data.root, data.paths);
expect(value).toBeDefined();
```
### 4. parent[key] 關係正確
```typescript
if (data.key !== undefined) {
expect(data.parent[data.key]).toBeDefined();
}
```
## 工具函數參考
### test/lib/tmp-runtime-data.ts
| 函數 | 用途 |
|------|------|
| `_createTmpRuntimeDataCapturer` | 建立自訂 isMergeableObject 來捕捉 tmpRuntimeData |
| `_getValidTmpRuntimeData` | 過濾掉 undefined 值 |
| `_validateTmpRuntimeData` | 驗證單一 tmpRuntimeData 的正確性 |
| `_validateAllTmpRuntimeData` | 批次驗證多個 tmpRuntimeData |
| `_findByPath` | 按路徑條件查找 tmpRuntimeData |
| `_filterByPathLength` | 按路徑長度篩選 tmpRuntimeData |
## 注意事項
1. **最深層可能為 undefined**:在最深層的 deepmerge 內,tmpRuntimeData 有可能是 undefined,這是正常行為。
2. **Root 可能是新物件**:deepmerge 內部會建立新的 destination 物件作為 root,這個物件會包含合併後的結果。
3. **僅供內部使用**:ITmpRuntimeData 是內部資料結構,主要用於讓自訂合併函式取得上下文資訊。
## 相關檔案
- `src/index.ts` - 類型定義與主要邏輯
- `test/tmp-runtime-data.test.ts` - 測試檔案
- `test/lib/tmp-runtime-data.ts` - 測試工具函數