mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-11-01 15:40:43 +00:00
fix(data-source): 依赖缓存按节点对象引用判断失效
此前缓存键由节点 id 拼接而成,节点被替换(如修改绑定字段)后 id 不变仍命中旧缓存, 导致新依赖未被收集。改为以首节点为 WeakMap key 并比较节点对象引用, 节点替换后重新收集依赖,节点回收后缓存自动释放
This commit is contained in:
parent
dfad691493
commit
6f2ffc3ca1
@ -1,4 +1,4 @@
|
||||
import type { DataSourceSchema, Id, MNode } from '@tmagic/core';
|
||||
import type { DataSourceSchema, MNode } from '@tmagic/core';
|
||||
import {
|
||||
DSL_NODE_KEY_COPY_PREFIX,
|
||||
isDataSourceCondTarget,
|
||||
@ -8,26 +8,43 @@ import {
|
||||
Watcher,
|
||||
} from '@tmagic/core';
|
||||
|
||||
const cache = new Map();
|
||||
interface CacheEntry {
|
||||
/** 计算依赖时的节点引用,用于判断节点是否被替换过 */
|
||||
refs: MNode[];
|
||||
result: any;
|
||||
}
|
||||
|
||||
export const getDeps = (ds: DataSourceSchema, nodes: MNode[], inEditor: boolean) => {
|
||||
let cacheKey: string;
|
||||
/**
|
||||
* 以第一个节点对象作为 WeakMap 的 key,节点被回收后缓存自动释放,
|
||||
* 不同数据源 / 是否在编辑器中的结果存放在内层 Map。
|
||||
* 通过比较节点对象引用(而不是 id)来判断缓存是否有效,节点被替换后(如修改了绑定的字段)会重新收集依赖
|
||||
*/
|
||||
const cache = new WeakMap<MNode, Map<string, CacheEntry>>();
|
||||
|
||||
if (inEditor) {
|
||||
const ids: Id[] = [];
|
||||
nodes.forEach((node) => {
|
||||
traverseNode(node, (node) => {
|
||||
ids.push(node.id);
|
||||
});
|
||||
});
|
||||
|
||||
cacheKey = `${ds.id}:${ids.join(':')}`;
|
||||
} else {
|
||||
cacheKey = `${ds.id}:${nodes.map((node) => node.id).join(':')}`;
|
||||
const getRefs = (nodes: MNode[], inEditor: boolean) => {
|
||||
if (!inEditor) {
|
||||
return [...nodes];
|
||||
}
|
||||
|
||||
if (cache.has(cacheKey)) {
|
||||
return cache.get(cacheKey);
|
||||
const refs: MNode[] = [];
|
||||
nodes.forEach((node) => {
|
||||
traverseNode(node, (item) => {
|
||||
refs.push(item);
|
||||
});
|
||||
});
|
||||
return refs;
|
||||
};
|
||||
|
||||
const isSameRefs = (a: MNode[], b: MNode[]) => a.length === b.length && a.every((item, index) => item === b[index]);
|
||||
|
||||
export const getDeps = (ds: DataSourceSchema, nodes: MNode[], inEditor: boolean) => {
|
||||
const refs = getRefs(nodes, inEditor);
|
||||
const cacheKey = `${ds.id}:${inEditor}`;
|
||||
const rootNode = nodes[0];
|
||||
|
||||
const entry = rootNode ? cache.get(rootNode)?.get(cacheKey) : undefined;
|
||||
if (entry && isSameRefs(entry.refs, refs)) {
|
||||
return entry.result;
|
||||
}
|
||||
|
||||
const watcher = new Watcher();
|
||||
@ -60,7 +77,11 @@ export const getDeps = (ds: DataSourceSchema, nodes: MNode[], inEditor: boolean)
|
||||
|
||||
const result = { deps, condDeps };
|
||||
|
||||
cache.set(cacheKey, result);
|
||||
if (rootNode) {
|
||||
const entries = cache.get(rootNode) ?? new Map<string, CacheEntry>();
|
||||
entries.set(cacheKey, { refs, result });
|
||||
cache.set(rootNode, entries);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -54,4 +54,61 @@ describe('getDeps', () => {
|
||||
const r2 = getDeps(ds, nodes, false);
|
||||
expect(r1).toBe(r2);
|
||||
});
|
||||
|
||||
const ds: any = { id: 'ds_ref', fields: [{ name: 'list', type: 'array', fields: [{ name: 'name' }] }] };
|
||||
|
||||
test('节点对象不变(即使放进新数组)时命中缓存', () => {
|
||||
const node: any = { id: 'n_hit', type: 'text', text: '${ds_ref.list.name}' };
|
||||
const r1 = getDeps(ds, [node], false);
|
||||
const r2 = getDeps(ds, [node], false);
|
||||
expect(r1).toBe(r2);
|
||||
expect(r1.deps.n_hit.keys).toEqual(['text']);
|
||||
});
|
||||
|
||||
test.each([false, true])('同 id 的节点被替换且绑定的字段变了,重新收集依赖 (inEditor=%s)', (inEditor) => {
|
||||
const before: any = { id: 'n_replaced', type: 'text', text: '${ds_ref.list.name}', title: 'plain' };
|
||||
const after: any = { id: 'n_replaced', type: 'text', text: 'plain', title: '${ds_ref.list.name}' };
|
||||
|
||||
const r1 = getDeps(ds, [before], inEditor);
|
||||
const r2 = getDeps(ds, [after], inEditor);
|
||||
|
||||
expect(r1.deps.n_replaced.keys).toEqual(['text']);
|
||||
expect(r2.deps.n_replaced.keys).toEqual(['title']);
|
||||
});
|
||||
|
||||
test('inEditor=true 时子节点被替换(父节点不变)也会重新收集', () => {
|
||||
const parent: any = {
|
||||
id: 'n_parent',
|
||||
type: 'container',
|
||||
items: [{ id: 'n_child', type: 'text', text: '${ds_ref.list.name}' }],
|
||||
};
|
||||
const r1 = getDeps(ds, [parent], true);
|
||||
expect(r1.deps.n_child.keys).toEqual(['text']);
|
||||
|
||||
parent.items.splice(0, 1, { id: 'n_child', type: 'text', title: '${ds_ref.list.name}' });
|
||||
const r2 = getDeps(ds, [parent], true);
|
||||
expect(r2.deps.n_child.keys).toEqual(['title']);
|
||||
});
|
||||
|
||||
test('不同数据源、不同 inEditor 的结果互不影响', () => {
|
||||
const node: any = { id: 'n_multi', type: 'text', text: '${ds_ref.list.name} ${ds_other.list.name}' };
|
||||
const other: any = { id: 'ds_other', fields: [{ name: 'list', type: 'array', fields: [{ name: 'name' }] }] };
|
||||
|
||||
const a = getDeps(ds, [node], false);
|
||||
const b = getDeps(other, [node], false);
|
||||
const c = getDeps(ds, [node], true);
|
||||
|
||||
expect(a).not.toBe(b);
|
||||
expect(a).not.toBe(c);
|
||||
expect(getDeps(ds, [node], false)).toBe(a);
|
||||
expect(getDeps(other, [node], false)).toBe(b);
|
||||
expect(getDeps(ds, [node], true)).toBe(c);
|
||||
});
|
||||
|
||||
test('nodes 为空时不缓存也不报错', () => {
|
||||
const r1 = getDeps(ds, [], false);
|
||||
const r2 = getDeps(ds, [], false);
|
||||
expect(r1.deps).toEqual({});
|
||||
expect(r2.deps).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user