mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-09-12 15:09:43 +00:00
fix(editor): 丢弃过期 root 快照,避免画布回退与 modelValue 回写死循环
This commit is contained in:
parent
7edb01d330
commit
66b54a52f8
@ -427,12 +427,26 @@ export const initServiceEvents = (
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* root 是否已经不是编辑器当前的 root。
|
||||
*
|
||||
* `editorService.set('root', v)` 是同步赋值后再派发 `root-change`,因此派发那一刻 `v` 一定
|
||||
* 就是当前 root;但处理 `root-change` 要等 stage / runtime / 依赖收集等异步过程,期间 root
|
||||
* 可能被新的一次整体替换(外部重设 DSL、更新 root 节点、源码保存等)顶掉,此时手上的 `v`
|
||||
* 已是过期快照,继续用它刷画布或回写给外部都会与新 root 互相覆盖。
|
||||
*/
|
||||
const isStaleRoot = (value: MApp | null) => toRaw(editorService.get('root')) !== toRaw(value);
|
||||
|
||||
const updateStageDsl = async (value: MApp | null) => {
|
||||
const stage = await getStage();
|
||||
|
||||
const runtime = await stage.renderer?.getRuntime();
|
||||
const app = await getTMagicApp();
|
||||
|
||||
// 等 stage / runtime 就绪期间 root 已被替换:新 root 的刷新可能已经完成,
|
||||
// 再把旧 dsl 推给 runtime 会让画布回退到旧内容
|
||||
if (isStaleRoot(value)) return;
|
||||
|
||||
if (!app?.dataSourceManager) {
|
||||
runtime?.updateRootConfig?.(cloneDeep(toRaw(value))!);
|
||||
}
|
||||
@ -449,6 +463,8 @@ export const initServiceEvents = (
|
||||
|
||||
await (typeof Worker === 'undefined' ? collectIdle(value.items, true) : depService.collectByWorker(value));
|
||||
|
||||
if (isStaleRoot(value)) return;
|
||||
|
||||
const dsl = cloneDeep(toRaw(value));
|
||||
if (dsl.dataSources && dsl.dataSourceDeps && app?.dataSourceManager) {
|
||||
for (const node of getNodes(getDepNodeIds(dsl.dataSourceDeps), dsl.items)) {
|
||||
@ -466,7 +482,7 @@ export const initServiceEvents = (
|
||||
depService.addTarget(createDataSourceCondTarget(ds, reactive({})));
|
||||
};
|
||||
|
||||
const rootChangeHandler = (value: MApp | null, preValue?: MApp | null) => {
|
||||
const rootChangeHandler = (value: MApp | null) => {
|
||||
if (!value) return;
|
||||
|
||||
value.codeBlocks = value.codeBlocks || {};
|
||||
@ -509,7 +525,13 @@ export const initServiceEvents = (
|
||||
editorService.set('page', null);
|
||||
}
|
||||
|
||||
if (toRaw(value) !== toRaw(preValue)) {
|
||||
// 上面的 select 是异步的,期间 root 可能已被替换,过期快照不能再回写给外部:
|
||||
// 两次整体替换各自持有一个快照时,回写会把对方的 root 顶掉,外部 modelValue 变化又会
|
||||
// 重新 set root,两条链路无休止地交替下去(表现为编辑器卡死)
|
||||
if (isStaleRoot(value)) return;
|
||||
|
||||
// 外部已经持有这个 root(如本次变化就是 modelValue 传进来的)时无需回写
|
||||
if (toRaw(props.modelValue) !== toRaw(value)) {
|
||||
emit('update:modelValue', value);
|
||||
}
|
||||
})();
|
||||
|
||||
@ -357,7 +357,6 @@ describe('initServiceEvents', () => {
|
||||
});
|
||||
|
||||
test('rootChange 处理代码块和数据源', async () => {
|
||||
services.editorService.state.root = { id: 'r' };
|
||||
mount(WrapEvents({} as any, emit, services));
|
||||
const value: any = {
|
||||
id: 'r',
|
||||
@ -365,6 +364,8 @@ describe('initServiceEvents', () => {
|
||||
dataSources: [{ id: 'd1', type: 'base' }],
|
||||
items: [],
|
||||
};
|
||||
// set('root', v) 是先赋值再派发事件,mock 中同样先对齐 state 再 emit
|
||||
services.editorService.state.root = value;
|
||||
services.editorService.emit('root-change', value, null);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(services.codeBlockService.setCodeDsl).toHaveBeenCalled();
|
||||
@ -593,13 +594,15 @@ describe('initServiceEvents', () => {
|
||||
services.editorService.state.node = { id: 'n1' };
|
||||
mount(WrapEvents({} as any, emit, services));
|
||||
|
||||
services.editorService.emit('root-change', {
|
||||
const value: any = {
|
||||
id: 'r',
|
||||
items: [{ id: 'n1', type: 'text' }],
|
||||
dataSources: [],
|
||||
dataSourceDeps: { d1: {} },
|
||||
codeBlocks: {},
|
||||
});
|
||||
};
|
||||
services.editorService.state.root = value;
|
||||
services.editorService.emit('root-change', value);
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
|
||||
expect(stage.runtime.updatePageId).toHaveBeenCalledWith('p1');
|
||||
@ -608,10 +611,10 @@ describe('initServiceEvents', () => {
|
||||
});
|
||||
|
||||
test('rootChange items 不是数组时清空依赖', async () => {
|
||||
services.editorService.state.root = { id: 'r' };
|
||||
mount(WrapEvents({} as any, emit, services));
|
||||
|
||||
const value: any = { id: 'r', dataSourceDeps: { a: {} }, dataSourceCondDeps: { b: {} } };
|
||||
services.editorService.state.root = value;
|
||||
services.editorService.emit('root-change', value);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
@ -625,6 +628,7 @@ describe('initServiceEvents', () => {
|
||||
mount(WrapEvents({} as any, emit, services));
|
||||
|
||||
const value: any = { id: 'r', items: [] };
|
||||
services.editorService.state.root = value;
|
||||
services.editorService.emit('root-change', value, { id: 'prev' });
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
@ -638,12 +642,44 @@ describe('initServiceEvents', () => {
|
||||
services.editorService.getNodeById.mockReturnValue(null);
|
||||
mount(WrapEvents({} as any, emit, services));
|
||||
|
||||
services.editorService.emit('root-change', { id: 'r', items: [{ id: 'first', type: 'page' }] });
|
||||
const value: any = { id: 'r', items: [{ id: 'first', type: 'page' }] };
|
||||
services.editorService.state.root = value;
|
||||
services.editorService.emit('root-change', value);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
expect(services.editorService.select).toHaveBeenCalledWith({ id: 'first', type: 'page' });
|
||||
});
|
||||
|
||||
test('rootChange 外部已持有该 root 时不回写 modelValue', async () => {
|
||||
services.editorService.getNodeById.mockReturnValue(null);
|
||||
const value: any = { id: 'r', items: [] };
|
||||
mount(WrapEvents({ modelValue: value } as any, emit, services));
|
||||
|
||||
services.editorService.state.root = value;
|
||||
services.editorService.emit('root-change', value);
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
expect(emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('rootChange 处理期间 root 被替换:不刷旧 dsl 也不回写过期快照', async () => {
|
||||
const app: any = { dsl: {}, dataSourceManager: mkDataSourceManager() };
|
||||
const stage = mkReadyStage(app);
|
||||
services.editorService.state.stage = stage;
|
||||
services.editorService.getNodeById.mockReturnValue(null);
|
||||
mount(WrapEvents({} as any, emit, services));
|
||||
|
||||
const value: any = { id: 'r', items: [{ id: 'n1', type: 'text' }], dataSources: [], codeBlocks: {} };
|
||||
services.editorService.state.root = value;
|
||||
services.editorService.emit('root-change', value);
|
||||
// 异步处理还没跑完,root 已被新的一次整体替换顶掉
|
||||
services.editorService.state.root = { id: 'r', items: [], dataSources: [], codeBlocks: {} };
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
|
||||
expect(stage.runtime.updateRootConfig).not.toHaveBeenCalled();
|
||||
expect(emit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('update 事件:ROOT 节点、无 propPath、命中已收集依赖三种分支', async () => {
|
||||
services.editorService.state.root = { id: 'r', items: [] };
|
||||
services.depService.getTargets.mockReturnValue({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user