Skip to content

Commit 8220550

Browse files
authored
feat: avoid layouts thrashing (#3764)
1 parent 9c91ec1 commit 8220550

File tree

34 files changed

+880
-380
lines changed

34 files changed

+880
-380
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { fastdom } from '@opensumi/ide-core-browser';
2+
3+
/**
4+
* 用于 mock requestAnimationFrame 的表现,可以手动触发动画帧
5+
*/
6+
class AnimationFrameController {
7+
private callbacks: Array<() => void> = [];
8+
private currentFrame: number = 0;
9+
private running: boolean = false;
10+
11+
constructor() {
12+
this.run = this.run.bind(this);
13+
}
14+
15+
/**
16+
* 触发动画帧
17+
*/
18+
public run() {
19+
if (this.running) {
20+
return;
21+
}
22+
this.running = true;
23+
this.callbacks.forEach((callback) => {
24+
callback();
25+
});
26+
this.running = false;
27+
this.currentFrame++;
28+
}
29+
30+
/**
31+
* 注册回调函数
32+
* @param callback
33+
*/
34+
35+
public register(callback: () => void) {
36+
this.callbacks.push(callback);
37+
}
38+
39+
/**
40+
* 注销回调函数
41+
* @param callback
42+
*/
43+
44+
public unregister(callback: () => void) {
45+
const index = this.callbacks.indexOf(callback);
46+
if (index !== -1) {
47+
this.callbacks.splice(index, 1);
48+
}
49+
}
50+
51+
/**
52+
* 获取当前的动画帧
53+
*/
54+
public getCurrentFrame() {
55+
return this.currentFrame;
56+
}
57+
}
58+
59+
describe('fastdom', () => {
60+
it('should measure', () => {
61+
const animationFrameController = new AnimationFrameController();
62+
63+
let originalAnimationFrame = global.requestAnimationFrame;
64+
let originalCancelAnimationFrame = global.cancelAnimationFrame;
65+
66+
global.requestAnimationFrame = (callback) => {
67+
animationFrameController.register(callback);
68+
return 1;
69+
};
70+
71+
global.cancelAnimationFrame = () => {};
72+
let count = 0;
73+
fastdom.measure(() => {
74+
count++;
75+
expect(count).toBe(1);
76+
77+
fastdom.measure(() => {
78+
// will run on current frame
79+
count++;
80+
expect(count).toBe(3);
81+
});
82+
83+
fastdom.measureAtNextFrame(() => {
84+
// will run on next frame
85+
count++;
86+
expect(count).toBe(4);
87+
88+
fastdom.measure(() => {
89+
count += 2;
90+
expect(count).toBe(6);
91+
});
92+
fastdom.mutate(() => {
93+
count += 3;
94+
expect(count).toBe(9);
95+
});
96+
});
97+
});
98+
99+
fastdom.mutate(() => {
100+
count++;
101+
expect(count).toBe(2);
102+
});
103+
104+
animationFrameController.run();
105+
animationFrameController.run();
106+
expect(count).toBe(9);
107+
animationFrameController.run();
108+
animationFrameController.run();
109+
expect(count).toBe(9);
110+
111+
global.requestAnimationFrame = originalAnimationFrame;
112+
global.cancelAnimationFrame = originalCancelAnimationFrame;
113+
});
114+
});

packages/core-browser/src/bootstrap/app.view.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export function App(props: AppProps) {
3838
lastFrame = null;
3939
allSlot.forEach((item) => {
4040
eventBus.fire(new ResizeEvent({ slotLocation: item.slot }));
41+
eventBus.fireDirective(ResizeEvent.createDirective(item.slot));
4142
});
4243
});
4344
};

packages/core-browser/src/components/layout/split-panel.tsx

+8-7
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ export const SplitPanel: React.FC<SplitPanelProps> = (props) => {
224224
(location?: string) => {
225225
if (location) {
226226
eventBus.fire(new ResizeEvent({ slotLocation: location }));
227+
eventBus.fireDirective(ResizeEvent.createDirective(location));
227228
}
228229
},
229230
[eventBus],
@@ -332,20 +333,19 @@ export const SplitPanel: React.FC<SplitPanelProps> = (props) => {
332333
if (rootRef.current) {
333334
splitPanelService.setRootNode(rootRef.current);
334335
}
335-
const disposer = eventBus.on(ResizeEvent, (e) => {
336-
if (e.payload.slotLocation === id) {
337-
childList.forEach((c) => {
338-
fireResizeEvent(getProp(c, 'slot') || getProp(c, 'id'));
339-
});
340-
}
336+
const disposer = eventBus.onDirective(ResizeEvent.createDirective(id), () => {
337+
childList.forEach((c) => {
338+
fireResizeEvent(getProp(c, 'slot') || getProp(c, 'id'));
339+
});
341340
});
342341
return () => {
343342
disposer.dispose();
344343
};
345344
}, []);
346345

347346
const renderSplitPanel = React.useMemo(() => {
348-
const { minResize, flexGrow, minSize, maxSize, savedSize, defaultSize, flex, noResize, slot, ...rest } = props;
347+
const { minResize, flexGrow, minSize, maxSize, savedSize, defaultSize, flex, noResize, slot, headerSize, ...rest } =
348+
props;
349349

350350
delete rest['resizeHandleClassName'];
351351
delete rest['dynamicTarget'];
@@ -364,6 +364,7 @@ export const SplitPanel: React.FC<SplitPanelProps> = (props) => {
364364
data-max-size={maxSize}
365365
data-saved-size={savedSize}
366366
data-default-size={defaultSize}
367+
data-header-size={headerSize}
367368
data-flex={flex}
368369
data-flex-grow={flexGrow}
369370
data-no-resize={noResize}

0 commit comments

Comments
 (0)