mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Re-use rAF, don't re-schedule it
This commit is contained in:
+47
-25
@@ -414,37 +414,59 @@ export const scheduleMicrotask: any =
|
||||
.catch(handleErrorInNextTick)
|
||||
: scheduleTimeout; // TODO: Determine the best fallback here.
|
||||
|
||||
// -------------------
|
||||
// requestAnimationFrame
|
||||
// -------------------
|
||||
type FrameAlignedTask = {
|
||||
frameNode: any,
|
||||
callbackNode: any,
|
||||
};
|
||||
|
||||
// TODO: Fix these types
|
||||
export const supportsFrameAlignedTask = true;
|
||||
export function scheduleFrameAlignedTask(task: any): FrameAlignedTask {
|
||||
// Schedule both tasks, we'll race them and use the first to fire.
|
||||
const raf: any = localRequestAnimationFrame;
|
||||
|
||||
return {
|
||||
frameNode: raf(task),
|
||||
callbackNode: Scheduler.unstable_scheduleCallback(
|
||||
Scheduler.unstable_NormalPriority,
|
||||
task,
|
||||
),
|
||||
};
|
||||
type FrameAlignedTask = {|
|
||||
rafNode: number,
|
||||
schedulerNode: number,
|
||||
task: function,
|
||||
|};
|
||||
|
||||
let currentTask: FrameAlignedTask | null = null;
|
||||
function performFrameAlignedWork() {
|
||||
if (currentTask != null) {
|
||||
const task = currentTask.task;
|
||||
localCancelAnimationFrame(currentTask.id);
|
||||
Scheduler.unstable_cancelCallback(currentTask.schedulerNode);
|
||||
currentTask = null;
|
||||
if (task != null) {
|
||||
task();
|
||||
}
|
||||
}
|
||||
}
|
||||
export function cancelFrameAlignedTask(task: any) {
|
||||
const caf: any = localCancelAnimationFrame;
|
||||
if (task.frameNode != null) {
|
||||
caf(task.frameNode);
|
||||
|
||||
export function scheduleFrameAlignedTask(task: any): any {
|
||||
if (currentTask === null) {
|
||||
const rafNode = localRequestAnimationFrame(performFrameAlignedWork);
|
||||
|
||||
const schedulerNode = Scheduler.unstable_scheduleCallback(
|
||||
Scheduler.unstable_NormalPriority,
|
||||
performFrameAlignedWork,
|
||||
);
|
||||
|
||||
currentTask = {
|
||||
rafNode,
|
||||
schedulerNode,
|
||||
task,
|
||||
};
|
||||
} else {
|
||||
currentTask.task = task;
|
||||
currentTask.schedulerNode = Scheduler.unstable_scheduleCallback(
|
||||
Scheduler.unstable_NormalPriority,
|
||||
performFrameAlignedWork,
|
||||
);
|
||||
}
|
||||
|
||||
if (task.callbackNode != null) {
|
||||
Scheduler.unstable_cancelCallback(task.callbackNode);
|
||||
}
|
||||
return currentTask;
|
||||
}
|
||||
|
||||
export function cancelFrameAlignedTask(task: FrameAlignedTask) {
|
||||
Scheduler.unstable_cancelCallback(task.schedulerNode);
|
||||
task.schedulerNode = null;
|
||||
// We don't cancel the rAF in case it gets re-used later.
|
||||
// But clear the task so if it fires and shouldn't run, it won't.
|
||||
task.task = null;
|
||||
}
|
||||
|
||||
function handleErrorInNextTick(error) {
|
||||
|
||||
@@ -618,6 +618,44 @@ describe('ReactDOMFiberAsync', () => {
|
||||
expect(counterRef.current.textContent).toBe('Count: 2');
|
||||
});
|
||||
|
||||
// @gate enableFrameEndScheduling
|
||||
it('Should re-use scheduled rAF, not cancel and schedule anew', () => {
|
||||
let setState = null;
|
||||
let counterRef = null;
|
||||
function Counter() {
|
||||
const [count, setCount] = React.useState(0);
|
||||
const ref = React.useRef();
|
||||
setState = setCount;
|
||||
counterRef = ref;
|
||||
Scheduler.unstable_yieldValue('Count: ' + count);
|
||||
return <p ref={ref}>Count: {count}</p>;
|
||||
}
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
act(() => {
|
||||
root.render(<Counter />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Count: 0']);
|
||||
|
||||
window.event = undefined;
|
||||
setState(1);
|
||||
// Unknown updates should schedule a rAF.
|
||||
expect(global.requestAnimationFrameQueue.length).toBe(1);
|
||||
const firstRaf = global.requestAnimationFrameQueue[0];
|
||||
|
||||
setState(2);
|
||||
// Default updates after unknown should re-use the scheduled rAF.
|
||||
expect(global.requestAnimationFrameQueue.length).toBe(1);
|
||||
const secondRaf = global.requestAnimationFrameQueue[0];
|
||||
expect(firstRaf).toBe(secondRaf);
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
expect(counterRef.current.textContent).toBe('Count: 0');
|
||||
global.flushRequestAnimationFrameQueue();
|
||||
expect(Scheduler).toHaveYielded(['Count: 2']);
|
||||
expect(counterRef.current.textContent).toBe('Count: 2');
|
||||
});
|
||||
|
||||
// @gate enableFrameEndScheduling
|
||||
it('Default update followed by an unknown update is batched, scheduled in a rAF', () => {
|
||||
let setState = null;
|
||||
|
||||
@@ -925,9 +925,13 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
|
||||
if (
|
||||
enableFrameEndScheduling &&
|
||||
newCallbackPriority === DefaultLane &&
|
||||
existingCallbackNode !== null &&
|
||||
// TODO: We can't expose the rafNode here,
|
||||
// but how do we know the rAF is not scheduled?
|
||||
existingCallbackNode.rafNode == null &&
|
||||
root.hasUnknownUpdates
|
||||
) {
|
||||
// Do nothing, we need to cancel the existing default task and schedule a rAF.
|
||||
// Do nothing, we need to schedule a new rAF.
|
||||
} else {
|
||||
// The priority hasn't changed. We can reuse the existing task. Exit.
|
||||
return;
|
||||
@@ -940,8 +944,9 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
|
||||
enableFrameEndScheduling &&
|
||||
supportsFrameAlignedTask &&
|
||||
existingCallbackNode != null &&
|
||||
// TODO: is there a better check for callbackNode type?
|
||||
existingCallbackNode.frameNode != null
|
||||
// TODO: we can't expose the scheduler node here,
|
||||
// but how do we know we need to cancel with the host config method?
|
||||
existingCallbackNode.schedulerNode != null
|
||||
) {
|
||||
cancelFrameAlignedTask(existingCallbackNode);
|
||||
} else {
|
||||
|
||||
@@ -925,9 +925,13 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
|
||||
if (
|
||||
enableFrameEndScheduling &&
|
||||
newCallbackPriority === DefaultLane &&
|
||||
existingCallbackNode !== null &&
|
||||
// TODO: We can't expose the rafNode here,
|
||||
// but how do we know the rAF is not scheduled?
|
||||
existingCallbackNode.rafNode == null &&
|
||||
root.hasUnknownUpdates
|
||||
) {
|
||||
// Do nothing, we need to cancel the existing default task and schedule a rAF.
|
||||
// Do nothing, we need to schedule a new rAF.
|
||||
} else {
|
||||
// The priority hasn't changed. We can reuse the existing task. Exit.
|
||||
return;
|
||||
@@ -940,8 +944,9 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
|
||||
enableFrameEndScheduling &&
|
||||
supportsFrameAlignedTask &&
|
||||
existingCallbackNode != null &&
|
||||
// TODO: is there a better check for callbackNode type?
|
||||
existingCallbackNode.frameNode != null
|
||||
// TODO: we can't expose the scheduler node here,
|
||||
// but how do we know we need to cancel with the host config method?
|
||||
existingCallbackNode.schedulerNode != null
|
||||
) {
|
||||
cancelFrameAlignedTask(existingCallbackNode);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user