mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-10-28 05:20:43 +00:00
fix(dep): 修复共用节点 id 的多个根节点收集依赖时互相清除
多个根节点(如复制出的页面片)共用节点 id 时,逐节点“删除旧依赖 + 收集”会让后一棵树的删除清掉前一棵树刚写入的依赖。改为先统一删除所有节点的旧依赖,再统一收集。
This commit is contained in:
parent
d01bc45b17
commit
c4cf3b9525
@ -133,9 +133,13 @@ export default class Watcher {
|
||||
return;
|
||||
}
|
||||
|
||||
// 整棵树只遍历一次、在每个属性上检查所有 target,把结构遍历开销从 ×targets 降到 ×1(详见 collectItems)
|
||||
// 先清掉本次节点上的旧依赖,再统一收集。
|
||||
// 复制出的页面片会共用节点 id。如果在收集每一棵树之前立刻删除,
|
||||
// 后一棵树会按相同 id 把前一棵树刚写入的依赖清掉。
|
||||
for (const node of nodes) {
|
||||
this.removeTargetsDep(targets, node);
|
||||
}
|
||||
for (const node of nodes) {
|
||||
this.collectItems(node, targets, depExtendedData, deep);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,4 +249,86 @@ describe('Watcher', () => {
|
||||
expect(target1?.deps?.node_1).toBeUndefined();
|
||||
expect(target1?.deps?.node_2).toBeUndefined();
|
||||
});
|
||||
|
||||
test('collect keeps deps when roots share node ids', () => {
|
||||
const watcher = new Watcher();
|
||||
|
||||
watcher.addTarget(
|
||||
new Target({
|
||||
type: 'target',
|
||||
id: 'ds_a',
|
||||
isTarget: (key: string | number, value: any) => key === 'text' && value === 'a',
|
||||
}),
|
||||
);
|
||||
watcher.addTarget(
|
||||
new Target({
|
||||
type: 'target',
|
||||
id: 'ds_b',
|
||||
isTarget: (key: string | number, value: any) => key === 'text' && value === 'b',
|
||||
}),
|
||||
);
|
||||
|
||||
watcher.collect(
|
||||
[
|
||||
{
|
||||
id: 'fragment_a',
|
||||
name: 'fragmentA',
|
||||
items: [
|
||||
{
|
||||
id: 'shared',
|
||||
name: 'shared-a',
|
||||
text: 'a',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'fragment_b',
|
||||
name: 'fragmentB',
|
||||
items: [
|
||||
{
|
||||
id: 'shared',
|
||||
name: 'shared-b',
|
||||
text: 'b',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
{},
|
||||
true,
|
||||
);
|
||||
|
||||
const targetA = watcher.getTarget('ds_a', 'target');
|
||||
const targetB = watcher.getTarget('ds_b', 'target');
|
||||
|
||||
expect(targetA?.deps?.shared.keys).toEqual(['text']);
|
||||
expect(targetA?.deps?.shared.name).toBe('shared-a');
|
||||
expect(targetB?.deps?.shared.keys).toEqual(['text']);
|
||||
expect(targetB?.deps?.shared.name).toBe('shared-b');
|
||||
});
|
||||
|
||||
test('collect merges keys when one target matches shared-id nodes in several roots', () => {
|
||||
const watcher = new Watcher();
|
||||
|
||||
watcher.addTarget(
|
||||
new Target({
|
||||
type: 'target',
|
||||
id: 'ds',
|
||||
isTarget: (key: string | number, value: any) => key === 'text' && value === 'x',
|
||||
}),
|
||||
);
|
||||
|
||||
watcher.collect(
|
||||
[
|
||||
{ id: 'root_a', items: [{ id: 'shared', name: 'shared-a', text: 'x' }] },
|
||||
{ id: 'root_b', items: [{ id: 'shared', name: 'shared-b', title: 'y', text: 'x' }] },
|
||||
],
|
||||
{},
|
||||
true,
|
||||
);
|
||||
|
||||
const dep = watcher.getTarget('ds', 'target')?.deps?.shared;
|
||||
expect(dep?.keys).toEqual(['text']);
|
||||
// 同 id 的依赖只保留一条,name 取最后收集的节点
|
||||
expect(dep?.name).toBe('shared-b');
|
||||
});
|
||||
});
|
||||
|
||||
@ -150,8 +150,10 @@ class Dep extends BaseService {
|
||||
const targets = this.watcher.getCollectableTargets(type);
|
||||
|
||||
if (targets.length) {
|
||||
// 先统一删除所有节点的旧依赖,再统一收集。
|
||||
// 如果逐节点“删除 + 收集”,共用 id 的多棵树(如复制出的页面片),
|
||||
// 后一棵树的删除会清掉前一棵树刚写入的依赖。
|
||||
for (const node of nodes) {
|
||||
// 先删除原有依赖,再批量收集
|
||||
if (isPage(node)) {
|
||||
for (const target of targets) {
|
||||
this.removePageDep(target, depExtendedData);
|
||||
@ -159,6 +161,8 @@ class Dep extends BaseService {
|
||||
} else {
|
||||
this.watcher.removeTargetsDep(targets, node);
|
||||
}
|
||||
}
|
||||
for (const node of nodes) {
|
||||
this.watcher.collectItems(node, targets, depExtendedData, deep);
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,6 +227,49 @@ describe('Dep service', () => {
|
||||
expect(() => depService.collect([{ id: 'p1', type: 'page', items: [] }] as any, { pageId: 'p1' })).not.toThrow();
|
||||
});
|
||||
|
||||
const makeTextTarget = (id: string, text: string) =>
|
||||
new Target({
|
||||
id,
|
||||
type: DepTargetType.DEFAULT,
|
||||
isTarget: (key: string | number, value: any) => key === 'text' && value === text,
|
||||
});
|
||||
|
||||
const makeSharedIdFragments = () => [
|
||||
{ id: 'fragment_a', type: 'container', items: [{ id: 'shared', type: 'text', text: 'a' }] },
|
||||
{ id: 'fragment_b', type: 'container', items: [{ id: 'shared', type: 'text', text: 'b' }] },
|
||||
];
|
||||
|
||||
test('collect 多个根节点共用子节点 id 时不会互相清掉依赖', () => {
|
||||
const targetA = makeTextTarget('ds_a', 'a');
|
||||
const targetB = makeTextTarget('ds_b', 'b');
|
||||
depService.addTarget(targetA);
|
||||
depService.addTarget(targetB);
|
||||
|
||||
depService.collect(makeSharedIdFragments() as any, {}, true);
|
||||
|
||||
expect(targetA.deps.shared?.keys).toEqual(['text']);
|
||||
expect(targetB.deps.shared?.keys).toEqual(['text']);
|
||||
});
|
||||
|
||||
test('collect 多个 page 节点同 pageId 时不会互相清掉依赖', () => {
|
||||
const target = makeTextTarget('ds_a', 'a');
|
||||
depService.addTarget(target);
|
||||
target.deps.stale = { name: 'stale', keys: ['text'], data: { pageId: 'p' } };
|
||||
|
||||
depService.collect(
|
||||
[
|
||||
{ id: 'page_1', type: 'page', text: 'a', items: [] },
|
||||
{ id: 'page_2', type: 'page', items: [{ id: 'child', type: 'text', text: 'a' }] },
|
||||
] as any,
|
||||
{ pageId: 'p' },
|
||||
true,
|
||||
);
|
||||
|
||||
expect(target.deps.stale).toBeUndefined();
|
||||
expect(target.deps.page_1?.keys).toEqual(['text']);
|
||||
expect(target.deps.child?.keys).toEqual(['text']);
|
||||
});
|
||||
|
||||
test('collectNode 支持 page 与普通节点两条路径', () => {
|
||||
const target = makeTarget('node-target');
|
||||
depService.addTarget(target);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user