Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Wait for Hooks before merging] Scheduler.unstable_next #14756

Merged
merged 4 commits into from
Feb 6, 2019

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Feb 4, 2019

When a user initiates an action, they expect immediate feedback. They don't necessarily expect the action to finish immediately, but they do expect some sort of response from the UI within a timeframe that feels immediate, so they know their request was received.

In React, the recommended way to achieve this is to split expensive updates into two separate tasks. The first task contains work that needs to finish immediately. We refer to this as a "user blocking" update. An example is, when switching to a new tab in a tabbed interface, the new tab should change color immediately after it's clicked.

Everything that doesn't need to happen immediately (i.e. doesn't block the user) should happen in a second task. This task typically takes longer to finish, either because it's CPU intensive or because it relies on IO-bound resources. In the tab switching example, this includes loading and rendering the content that corresponds to the newly selected tab. We refer to this as a "normal" update, because this category tends to encompass most of the work in a React application.

To implement this pattern in React, you need a separate setState call for each of the two tasks. The first setState should happen directly inside the event handler. React will automatically infer that this update is user-blocking and schedule it at the corresponding priority.

The second update — the more expensive one that does the bulk of the work — should be wrapped in a call to Scheduler.next:

const onClick = () => {
  // Local update that renders immediately
  setHighlightedTab(tab);
  Scheduler.next(() => {
    // Navigation update that might take a bit longer to finish
    dispatch({type: 'switch_tab', tab});
  });
};

Updates initiated inside the function passed to Scheduler.next are scheduled with "normal" priority, instead of user-blocking priority. (The function itself is immediately invoked, a la batchedUpdates and flushSync.)

The name "next" is intentionally vague about its exact semantics. It's meant to imply that the task scheduled inside passed function happen after the task scheduled directly inside the event handler.

@sizebot
Copy link

sizebot commented Feb 5, 2019

React: size: 🔺+1.1%, gzip: 🔺+0.5%

ReactDOM: size: 🔺+0.4%, gzip: -0.4%

Details of bundled changes.

Comparing: d1326f4...5bc0696

react

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react.development.js +0.9% +0.4% 98.73 KB 99.62 KB 25.73 KB 25.83 KB UMD_DEV
react.production.min.js 🔺+1.1% 🔺+0.5% 12.14 KB 12.28 KB 4.71 KB 4.73 KB UMD_PROD
react.profiling.min.js +1.0% +0.4% 14.3 KB 14.44 KB 5.24 KB 5.26 KB UMD_PROFILING
react.development.js 0.0% 0.0% 62.06 KB 62.06 KB 16.71 KB 16.71 KB NODE_DEV
react.production.min.js 0.0% 0.0% 6.7 KB 6.7 KB 2.77 KB 2.77 KB NODE_PROD
React-dev.js 0.0% 0.0% 59.37 KB 59.37 KB 15.78 KB 15.78 KB FB_WWW_DEV

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js 0.0% 0.0% 751.18 KB 751.49 KB 171.69 KB 171.73 KB UMD_DEV
react-dom.production.min.js 🔺+0.4% -0.4% 104.6 KB 105.03 KB 34.04 KB 33.9 KB UMD_PROD
react-dom.profiling.min.js +0.3% +0.3% 107.59 KB 107.96 KB 34.66 KB 34.77 KB UMD_PROFILING
react-dom.development.js -0.0% -0.0% 746.24 KB 746.08 KB 170.27 KB 170.22 KB NODE_DEV
react-dom.production.min.js 🔺+0.5% -0.4% 104.65 KB 105.2 KB 33.48 KB 33.35 KB NODE_PROD
react-dom.profiling.min.js +0.5% +0.2% 107.76 KB 108.28 KB 34.09 KB 34.17 KB NODE_PROFILING
ReactDOM-dev.js -0.0% -0.0% 768.8 KB 768.74 KB 171.48 KB 171.45 KB FB_WWW_DEV
ReactDOM-prod.js 🔺+0.4% -0.0% 313.36 KB 314.52 KB 57.54 KB 57.54 KB FB_WWW_PROD
ReactDOM-profiling.js +0.1% +0.1% 320.52 KB 320.87 KB 58.88 KB 58.92 KB FB_WWW_PROFILING
react-dom-unstable-fire.development.js 0.0% 0.0% 751.53 KB 751.83 KB 171.83 KB 171.87 KB UMD_DEV
react-dom-unstable-fire.production.min.js 🔺+0.4% -0.4% 104.61 KB 105.05 KB 34.05 KB 33.91 KB UMD_PROD
react-dom-unstable-fire.profiling.min.js +0.3% +0.3% 107.6 KB 107.97 KB 34.67 KB 34.78 KB UMD_PROFILING
react-dom-unstable-fire.development.js -0.0% -0.0% 746.58 KB 746.43 KB 170.41 KB 170.36 KB NODE_DEV
react-dom-unstable-fire.production.min.js 🔺+0.5% -0.4% 104.66 KB 105.22 KB 33.49 KB 33.35 KB NODE_PROD
react-dom-unstable-fire.profiling.min.js +0.5% +0.2% 107.77 KB 108.29 KB 34.1 KB 34.18 KB NODE_PROFILING
ReactFire-dev.js -0.0% -0.0% 768.01 KB 767.95 KB 171.4 KB 171.37 KB FB_WWW_DEV
ReactFire-prod.js 🔺+0.3% 0.0% 301.95 KB 303.01 KB 55.2 KB 55.23 KB FB_WWW_PROD
ReactFire-profiling.js +0.1% +0.1% 309.03 KB 309.38 KB 56.57 KB 56.61 KB FB_WWW_PROFILING
react-dom-test-utils.development.js 0.0% 0.0% 45.88 KB 45.88 KB 12.63 KB 12.63 KB NODE_DEV
react-dom-unstable-native-dependencies.development.js 0.0% 0.0% 60.61 KB 60.61 KB 15.92 KB 15.92 KB UMD_DEV
react-dom-unstable-native-dependencies.development.js 0.0% -0.0% 60.29 KB 60.29 KB 15.79 KB 15.79 KB NODE_DEV
react-dom-server.browser.production.min.js 0.0% 0.0% 18.49 KB 18.49 KB 7.15 KB 7.15 KB NODE_PROD
ReactDOMServer-prod.js 0.0% -0.0% 44.65 KB 44.65 KB 10.34 KB 10.34 KB FB_WWW_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.3% 1.21 KB 1.21 KB 705 B 707 B UMD_PROD

react-art

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-art.development.js +0.1% +0.1% 530.02 KB 530.74 KB 115.31 KB 115.45 KB UMD_DEV
react-art.production.min.js 🔺+0.5% 0.0% 96.66 KB 97.09 KB 29.81 KB 29.82 KB UMD_PROD
react-art.development.js 0.0% 0.0% 461.55 KB 461.75 KB 98.21 KB 98.24 KB NODE_DEV
react-art.production.min.js 🔺+0.8% 🔺+0.1% 61.66 KB 62.14 KB 18.96 KB 18.99 KB NODE_PROD
ReactART-dev.js +0.1% 0.0% 470.52 KB 470.79 KB 97.54 KB 97.58 KB FB_WWW_DEV
ReactART-prod.js 🔺+0.2% -0.2% 189.26 KB 189.73 KB 32.27 KB 32.22 KB FB_WWW_PROD

react-native-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactNativeRenderer-dev.js 0.0% -0.0% 595.32 KB 595.35 KB 127.88 KB 127.88 KB RN_FB_DEV
ReactNativeRenderer-prod.js 🔺+0.3% -0.2% 245.27 KB 245.88 KB 43 KB 42.92 KB RN_FB_PROD
ReactNativeRenderer-profiling.js +0.3% +0.3% 251.4 KB 252.17 KB 44.3 KB 44.44 KB RN_FB_PROFILING
ReactNativeRenderer-dev.js 0.0% -0.0% 595.23 KB 595.27 KB 127.84 KB 127.84 KB RN_OSS_DEV
ReactNativeRenderer-prod.js 🔺+0.3% -0.2% 245.28 KB 245.9 KB 42.99 KB 42.91 KB RN_OSS_PROD
ReactNativeRenderer-profiling.js +0.3% +0.3% 251.41 KB 252.19 KB 44.29 KB 44.43 KB RN_OSS_PROFILING
ReactFabric-dev.js 0.0% -0.0% 586.17 KB 586.21 KB 125.61 KB 125.61 KB RN_FB_DEV
ReactFabric-prod.js -0.0% -0.2% 238.47 KB 238.35 KB 41.54 KB 41.46 KB RN_FB_PROD
ReactFabric-profiling.js +0.3% +0.3% 243.75 KB 244.52 KB 42.83 KB 42.98 KB RN_FB_PROFILING
ReactFabric-dev.js 0.0% -0.0% 586.08 KB 586.11 KB 125.58 KB 125.58 KB RN_OSS_DEV
ReactFabric-prod.js -0.0% -0.2% 238.48 KB 238.36 KB 41.53 KB 41.46 KB RN_OSS_PROD
ReactFabric-profiling.js +0.3% +0.3% 243.76 KB 244.53 KB 42.83 KB 42.97 KB RN_OSS_PROFILING

react-test-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-test-renderer.development.js +0.3% +0.2% 472.51 KB 473.83 KB 100.42 KB 100.63 KB UMD_DEV
react-test-renderer.production.min.js 🔺+0.8% 🔺+0.1% 62.98 KB 63.49 KB 19.42 KB 19.45 KB UMD_PROD
react-test-renderer.development.js 0.0% 0.0% 468.08 KB 468.27 KB 99.28 KB 99.3 KB NODE_DEV
react-test-renderer.production.min.js 🔺+0.9% -0.0% 62.61 KB 63.17 KB 19.14 KB 19.14 KB NODE_PROD
ReactTestRenderer-dev.js +0.1% 0.0% 477.71 KB 477.98 KB 98.92 KB 98.96 KB FB_WWW_DEV
react-test-renderer-shallow.development.js 0.0% -0.0% 36.84 KB 36.84 KB 9.38 KB 9.38 KB UMD_DEV
react-test-renderer-shallow.production.min.js 0.0% 0.0% 11.03 KB 11.03 KB 3.33 KB 3.33 KB UMD_PROD

react-reconciler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-reconciler.development.js -0.1% -0.1% 459.34 KB 458.77 KB 96.67 KB 96.59 KB NODE_DEV
react-reconciler.production.min.js 🔺+0.8% -0.1% 62.82 KB 63.35 KB 18.79 KB 18.77 KB NODE_PROD
react-reconciler-persistent.development.js -0.1% -0.1% 457.72 KB 457.15 KB 96.02 KB 95.94 KB NODE_DEV
react-reconciler-persistent.production.min.js 🔺+0.8% -0.1% 62.83 KB 63.36 KB 18.79 KB 18.78 KB NODE_PROD

scheduler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
scheduler.development.js +4.0% +1.6% 22.42 KB 23.32 KB 5.95 KB 6.04 KB NODE_DEV
scheduler.production.min.js 🔺+3.5% 🔺+1.3% 4.73 KB 4.89 KB 1.83 KB 1.85 KB NODE_PROD
Scheduler-dev.js +4.0% +1.5% 22.65 KB 23.56 KB 5.99 KB 6.08 KB FB_WWW_DEV
Scheduler-prod.js 🔺+4.4% 🔺+1.0% 13.4 KB 13.99 KB 2.86 KB 2.88 KB FB_WWW_PROD
scheduler-tracing.production.min.js 0.0% -0.3% 728 B 728 B 384 B 383 B NODE_PROD
scheduler-tracing.profiling.min.js 0.0% +0.1% 3.26 KB 3.26 KB 1000 B 1001 B NODE_PROFILING

Generated by 🚫 dangerJS

// If we're in the middle of rendering a tree, do not update at the same
// expiration time that is already rendering.
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
expirationTime -= 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that confused me when I was reading this: is this before or after the nextRenderExpirationTime?

// Outside of concurrent mode, updates are always synchronous.
expirationTime = Sync;
} else if (isWorking && !isCommitting) {
// During render phase, updates expire during as the current render.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"during the current render"? "as the current render"? "with the current render"?

@@ -264,6 +264,37 @@ function unstable_runWithPriority(priorityLevel, eventHandler) {
}
}

function unstable_next(eventHandler) {
Copy link
Contributor

@Jessidhia Jessidhia Feb 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is IIUC an inlined version of:

return runWithPriority(
  Math.min(NormalPriority, getCurrentPriorityLevel()),
  eventHandler
)

?

Would be nice to be able to treat it more as a continuation; there is already support for it in the scheduler after all... but continuations would inherit the current deadline, defeating the point. Would it work if the continuation itself was wrapped with wrapCallback?

At any rate adding non-imperative continuations to React event handlers is likely a breaking change.

passiveEffectCallbackHandle = schedulePassiveEffects(callback);
passiveEffectCallbackHandle = runWithPriority(NormalPriority, () => {
return schedulePassiveEffects(callback);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I was reading this code last week I was wondering how you'd be able to mess with the schedulePassiveEffects priority. Doesn't seem normally possible... unless you cause a Sync update I guess...

@acdlite acdlite changed the title [WIP] Scheduler.unstable_next [Wait for Hooks before merging] Scheduler.unstable_next Feb 5, 2019
Changes the implementation of syncUpdates, deferredUpdates, and
interactiveUpdates to use runWithPriority, so

This is the minimum integration between Scheduler and React needed to
unblock use of the Scheduler.next API.
Changes the implementation of syncUpdates, deferredUpdates, and
interactiveUpdates to use runWithPriority, so

This is the minimum integration between Scheduler and React needed to
unblock use of the Scheduler.next API.
@acdlite acdlite merged commit bc9818f into facebook:master Feb 6, 2019
bvaughn pushed a commit to bvaughn/react that referenced this pull request Feb 6, 2019
n8schloss pushed a commit to n8schloss/react that referenced this pull request Feb 8, 2019
* Add Scheduler.unstable_next

* Use Scheduler to prioritize updates

Changes the implementation of syncUpdates, deferredUpdates, and
interactiveUpdates to use runWithPriority, so

This is the minimum integration between Scheduler and React needed to
unblock use of the Scheduler.next API.

* Add Scheduler.unstable_next

* Use Scheduler to prioritize updates

Changes the implementation of syncUpdates, deferredUpdates, and
interactiveUpdates to use runWithPriority, so

This is the minimum integration between Scheduler and React needed to
unblock use of the Scheduler.next API.
acdlite added a commit to acdlite/react that referenced this pull request Mar 1, 2019
This PR introduced some bugs in concurrent mode during internal testing.
Until we figure out a proper solution, I'm going to try reverting it.

Not totally certain this is sufficient to unbreak the bugs we found, but
I'm using this branch to determine that.
acdlite added a commit that referenced this pull request Mar 1, 2019
* Revert #14756 changes to ReactFiberScheduler

This PR introduced some bugs in concurrent mode during internal testing.
Until we figure out a proper solution, I'm going to try reverting it.

Not totally certain this is sufficient to unbreak the bugs we found, but
I'm using this branch to determine that.

* Add back commented out Scheduler import

With a note not to use named imports next time we import Scheduler
in this module.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants