@nxtspace/immutable-diff
v0.0.1
Published
Compute RFC 6902 patch lists between Immutable.js collections.
Readme
@nxtspace/immutable-diff
计算两个 Immutable.js 数据结构之间的差异,产出 RFC 6902 形式的补丁列表(List<Map { op, path, value }>),可交给 @nxtspace/immutable-patch 应用。
安装
npm i @nxtspace/immutable-diffimmutable 为 peer dependency(^5),需由宿主项目自行提供。
快速开始
import { fromJS } from "immutable";
import diff from "@nxtspace/immutable-diff";
const a = fromJS({ x: 1, list: [1, 2, 3], nested: { k: "v" } });
const b = fromJS({ x: 2, list: [1, 3], nested: { k: "v2" } });
diff(a, b);
// List [
// Map { op: "replace", path: "/x", value: 2 },
// Map { op: "remove", path: "/list/1" },
// Map { op: "replace", path: "/nested/k", value: "v2" },
// ]与 patch 配做往返:
import patch from "@nxtspace/immutable-patch";
patch(a, diff(a, b)).equals(b); // trueAPI
默认导出与具名导出 diff 为同一函数。
diff(a, b, path?): List<PatchOperationMap>
| 参数 | 说明 |
| --- | --- |
| a | 原始数据结构:Map / List / Record / 基础类型均可 |
| b | 目标数据结构 |
| path | 父路径(内部递归使用,一般不传) |
返回 List<Map>,每条操作的 path 是 RFC 6901 JSON Pointer;op 只会是 add / remove / replace;remove 不携带 value 字段。
lcs(xs, ys): unknown[]
返回两个 Indexed 序列的最长公共子序列。
lcsDiff(xs, ys): LcsDiffEntry[]
具名导出为 diff,默认按 lcsDiff 引入。返回 op 为 "=" | "+" | "-" 的等值/增删记录,以及同位置的 "!=" 替换记录({ val, newVal })。
computeLcsMatrix(xs, ys): number[][]
返回 (n + 1) x (m + 1) 的动态规划表,供自定义回溯使用。
op(operation, path, value?): PatchOperation
构造单条普通对象形式的补丁(remove 时忽略 value)。
isMap(obj) / isIndexed(obj)
集合形态判断:Keyed(Map / OrderedMap)与 Indexed(List / Seq.Indexed)。
Path / escape / unescape / concat
JSON Pointer 片段工具,~ ⇄ ~0、/ ⇄ ~1;escape 额外支持数字 key(原样转字符串),concat(path, key) 返回 path + "/" + key。
类型
import type {
PatchOperationMap, // Map<string, unknown>,即 diff 的元素
PatchOperation, // 普通对象形式的补丁
OpType, // "add" | "remove" | "replace"
LcsDiffEntry,
} from "@nxtspace/immutable-diff";行为要点
- 序列(
List)差异基于 LCS 计算,产出按下标定位的add/remove/replace。 - 当
(a.count() + 1) * (b.count() + 1) >= 10000时,序列退化为按 key 逐位比较,避免 LCS 矩阵开销爆炸——此时差异粒度较粗(等长替换会逐位产出replace),但仍可被 patch 正确应用。 - 两侧
op判定使用immutable的is(),值相等的子树整体跳过。 a与b只有一个为null/undefined,或两者均非集合且不相等时,产出单条{ op: "replace", path: "/" }表示整体替换。
数据形态要求
两侧的集合必须整体 immutable(例如统一用 fromJS 构造)。若 List 内混入未转换的普通 JS 对象或数组:
- 这些元素之间永远按引用判定为不相等,会逐位产出大量
replace; - 返回值经过
fromJS,补丁里的value会被深转换成Map/List(而不是原对象引用)。
两者叠加会让 patch(a, diff(a, b)).equals(b) 不成立:
import { fromJS, List } from "immutable";
const mk = (n) => List(Array.from({ length: n }, (_, i) => ({ i }))); // 元素是普通对象
patch(mk(5), diff(mk(5), mk(6))).equals(mk(6)); // false
const clean = (n) => fromJS(Array.from({ length: n }, (_, i) => ({ i })));
patch(clean(5), diff(clean(5), clean(6))).equals(clean(6)); // trueLicense
MIT © Nxtspace
