Skip to content

Decouple update queue from Fiber type - #12600

Merged
acdlite merged 10 commits into
react:masterfrom
acdlite:refactor-updatequeue
Apr 23, 2018
Merged

Decouple update queue from Fiber type#12600
acdlite merged 10 commits into
react:masterfrom
acdlite:refactor-updatequeue

Conversation

@acdlite

@acdlite acdlite commented Apr 10, 2018

Copy link
Copy Markdown
Collaborator

The update queue is in need of a refactor. Recent bugfixes (#12528) have exposed some flaws in how it's modeled. Upcoming features like Suspense and [redacted] also rely on the update queue in ways that weren't anticipated in the original design.

Major changes:

  • Instead of boolean flags for isReplace and isForceUpdate, updates have a tag field (like Fiber). This lowers the cost for adding new types of updates.
  • Render phase updates are special cased. Updates scheduled during the render phase are dropped if the work-in-progress does not commit. This is used for getDerivedStateFrom{Props,Catch}.
  • callbackList has been replaced with a generic effect list. Aside from callbacks, this is also used for componentDidCatch.

TODO:

  • Class components
  • Host roots
  • Fix Flow
  • Update React DevTools dependency on hasForceUpdate (in a separate repo) I reverted this change

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is because we weren't counting componentDidCatch as a lifecycle previously

@pull-bot

pull-bot commented Apr 10, 2018

Copy link
Copy Markdown

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

Details of bundled changes.

Comparing: 999b656...e4ce363

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js +0.3% +0.6% 609.63 KB 611.61 KB 140.53 KB 141.35 KB UMD_DEV
react-dom.production.min.js 🔺+0.4% -0.2% 100.2 KB 100.56 KB 31.97 KB 31.9 KB UMD_PROD
react-dom.development.js +0.3% +0.6% 594 KB 595.99 KB 136.37 KB 137.22 KB NODE_DEV
react-dom.production.min.js 🔺+0.3% 0.0% 98.65 KB 98.99 KB 31.1 KB 31.11 KB NODE_PROD
react-dom-test-utils.development.js -0.0% 0.0% 40.75 KB 40.75 KB 11.69 KB 11.69 KB UMD_DEV
react-dom-test-utils.development.js -0.0% -0.0% 35.61 KB 35.61 KB 10.27 KB 10.27 KB NODE_DEV
ReactDOM-dev.js +0.3% +0.6% 618.41 KB 620.4 KB 139.14 KB 139.94 KB FB_WWW_DEV
ReactDOM-prod.js -0.1% -0.4% 284.94 KB 284.78 KB 52.3 KB 52.09 KB FB_WWW_PROD

react-art

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-art.development.js +0.5% +0.9% 412.73 KB 414.71 KB 89.59 KB 90.41 KB UMD_DEV
react-art.production.min.js 🔺+0.4% -0.3% 90.48 KB 90.85 KB 27.62 KB 27.54 KB UMD_PROD
react-art.development.js +0.6% +1.1% 338.56 KB 340.55 KB 70.81 KB 71.62 KB NODE_DEV
react-art.production.min.js 🔺+0.6% -0.5% 55.04 KB 55.37 KB 16.87 KB 16.79 KB NODE_PROD
ReactART-dev.js +0.6% +1.1% 346.81 KB 348.8 KB 70.42 KB 71.18 KB FB_WWW_DEV
ReactART-prod.js -0.1% -0.7% 167.61 KB 167.48 KB 27.75 KB 27.57 KB FB_WWW_PROD

react-test-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-test-renderer.development.js +0.6% +1.1% 345.69 KB 347.67 KB 72.47 KB 73.28 KB UMD_DEV
react-test-renderer.production.min.js 🔺+0.6% -0.2% 54.77 KB 55.13 KB 16.71 KB 16.68 KB UMD_PROD
react-test-renderer.development.js +0.6% +1.2% 336.51 KB 338.5 KB 69.66 KB 70.46 KB NODE_DEV
react-test-renderer.production.min.js 🔺+0.6% -0.1% 54.02 KB 54.36 KB 16.35 KB 16.33 KB NODE_PROD
ReactTestRenderer-dev.js +0.6% +1.1% 344.98 KB 346.97 KB 69.31 KB 70.09 KB FB_WWW_DEV

react-reconciler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-reconciler.development.js +0.6% +1.2% 316.9 KB 318.89 KB 65.12 KB 65.92 KB NODE_DEV
react-reconciler.production.min.js 🔺+0.7% -0.5% 46.91 KB 47.24 KB 14.3 KB 14.23 KB NODE_PROD
react-reconciler-persistent.development.js +0.6% +1.2% 316.23 KB 318.22 KB 64.88 KB 65.69 KB NODE_DEV
react-reconciler-persistent.production.min.js 🔺+0.7% 🔺+0.2% 45.83 KB 46.16 KB 14.08 KB 14.11 KB NODE_PROD
react-reconciler-reflection.development.js -0.0% 0.0% 11.06 KB 11.06 KB 3.42 KB 3.42 KB NODE_DEV

react-native-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactNativeRenderer-dev.js +0.4% +0.8% 456.63 KB 458.64 KB 97.38 KB 98.15 KB RN_FB_DEV
ReactNativeRenderer-prod.js -0.3% -0.7% 218.26 KB 217.69 KB 36.66 KB 36.39 KB RN_FB_PROD
ReactNativeRenderer-dev.js +0.4% +0.8% 456.38 KB 458.39 KB 97.32 KB 98.08 KB RN_OSS_DEV
ReactNativeRenderer-prod.js 🔺+0.9% -0.1% 215.01 KB 216.91 KB 36.29 KB 36.26 KB RN_OSS_PROD
ReactFabric-dev.js +0.5% +0.9% 439.04 KB 441.05 KB 92.94 KB 93.75 KB RN_FB_DEV
ReactFabric-prod.js 🔺+1.0% 🔺+0.3% 200.59 KB 202.5 KB 33.55 KB 33.65 KB RN_FB_PROD
ReactFabric-dev.js +0.5% +0.9% 439.08 KB 441.09 KB 92.96 KB 93.76 KB RN_OSS_DEV
ReactFabric-prod.js 🔺+1.0% 🔺+0.3% 200.63 KB 202.54 KB 33.57 KB 33.67 KB RN_OSS_PROD

Generated by 🚫 dangerJS

@gaearon

gaearon commented Apr 10, 2018

Copy link
Copy Markdown
Collaborator

Need to add “update DevTools” to todos. I think it might rely on isForceUpdate somewhere.

@bvaughn

bvaughn commented Apr 10, 2018

Copy link
Copy Markdown
Contributor

Need to add “update DevTools” to todos. I think it might rely on isForceUpdate somewhere.

I don't think so? I don't see any references to isForced.

@gaearon

gaearon commented Apr 10, 2018

Copy link
Copy Markdown
Collaborator

@acdlite

acdlite commented Apr 10, 2018

Copy link
Copy Markdown
Collaborator Author

Note: the bundle size changes are mostly because I haven't deleted the old update queue yet :D

@acdlite
acdlite force-pushed the refactor-updatequeue branch 3 times, most recently from e325697 to a99b4ca Compare April 11, 2018 02:23
@acdlite acdlite changed the title [WIP] Decouple update queue from Fiber type Decouple update queue from Fiber type Apr 11, 2018
@acdlite

acdlite commented Apr 11, 2018

Copy link
Copy Markdown
Collaborator Author

@sebmarkbage Ready for review

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't know why this wasn't already 0. There are no host effects or lifecycles in the second flush. New snapshot seems correct.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Same

@acdlite

acdlite commented Apr 11, 2018

Copy link
Copy Markdown
Collaborator Author

Needless to say, this is a risky change. I'll do the next www sync and fix any bugs that may come up.

@gaearon gaearon mentioned this pull request Apr 11, 2018
@NE-SmallTown

Copy link
Copy Markdown
Contributor

Upcoming features like Suspense and [redacted] also rely on the update queue in ways that weren't anticipated in the original design.

What does [redacted] mean?

@acdlite acdlite mentioned this pull request Apr 14, 2018
5 tasks

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is this change? You didn't replicate the other assignment so something is new.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

DRY for DRYness sake? Makes it harder to read this inline IMO. It also encourages reading the getDerivedStateFromProps property in multiple places if they're also needed at the callsite. Only abstract if it is chunk of work. (On the flip side, this PR successfully unabstract memoizeProps and memoizeState in places.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There was a reason we had to apply these effects in the complete phase. Why are we able to move them to the begin phase now?

@acdlite acdlite Apr 20, 2018

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't think there was a reason. It happened to be a convenient place to do it. Now we do it while processing the update queue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What are theses? They're not described in terms of side-effects. Either this needs to change, or the whole thing renamed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I really had the term "Shared". This has been sneaking into RN too.

It doesn't describe anything about the concept other than DRYness which indicates that maybe you should repeat yourself. If it is real concept, name it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Stop denying me muh mixins!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is clearly not going to be how we describe "redacted" so seems like a too early abstraction. Even just assuming that it is a single argument is a big assumption. Just use Fiber until you have something else.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You are forking into a first class concept with typeOfUpdateQueue then you're picking separate code paths again in commitEffect. The only thing gain by doing that instead of specializing each function is that you can reuse the code above here. Just break this out into a reusable function and instead specialize each code path without making typeOfUpdateQueue a first class thing.

@acdlite

acdlite commented Apr 18, 2018

Copy link
Copy Markdown
Collaborator Author

TODO after chatting to @sebmarkbage:

  • Use first-class functions instead of typeOfUpdateQueue
  • Separate effect list into "own" effects and children effects
  • Rename UpdateQueue side effect type to something that makes more sense
  • Remove ForceUpdate effect and go back to queue.hasForceUpdate instead

@acdlite
acdlite force-pushed the refactor-updatequeue branch 4 times, most recently from 995a437 to 73d4d48 Compare April 20, 2018 22:57
The update queue is in need of a refactor. Recent bugfixes (#12528) have
exposed some flaws in how it's modeled. Upcoming features like Suspense
and [redacted] also rely on the update queue in ways that weren't
anticipated in the original design.

Major changes:

- Instead of boolean flags for `isReplace` and `isForceUpdate`, updates
have a `tag` field (like Fiber). This lowers the cost for adding new
types of updates.
- Render phase updates are special cased. Updates scheduled during
the render phase are dropped if the work-in-progress does not commit.
This is used for `getDerivedStateFrom{Props,Catch}`.
- `callbackList` has been replaced with a generic effect list. Aside
from callbacks, this is also used for `componentDidCatch`.
@acdlite
acdlite force-pushed the refactor-updatequeue branch from 73d4d48 to 8830fd2 Compare April 20, 2018 23:04
I tried to avoid this at first, since we avoid it everywhere else in the Fiber
codebase, but since updates are not in a hot path, the trade off with file size
seems worth it.
mrizwanashiq pushed a commit to mrizwanashiq/react that referenced this pull request Jun 25, 2026
* Decouple update queue from Fiber type

The update queue is in need of a refactor. Recent bugfixes (react#12528) have
exposed some flaws in how it's modeled. Upcoming features like Suspense
and [redacted] also rely on the update queue in ways that weren't
anticipated in the original design.

Major changes:

- Instead of boolean flags for `isReplace` and `isForceUpdate`, updates
have a `tag` field (like Fiber). This lowers the cost for adding new
types of updates.
- Render phase updates are special cased. Updates scheduled during
the render phase are dropped if the work-in-progress does not commit.
This is used for `getDerivedStateFrom{Props,Catch}`.
- `callbackList` has been replaced with a generic effect list. Aside
from callbacks, this is also used for `componentDidCatch`.

* Remove first class UpdateQueue types and use closures instead

I tried to avoid this at first, since we avoid it everywhere else in the Fiber
codebase, but since updates are not in a hot path, the trade off with file size
seems worth it.

* Store captured errors on a separate part of the update queue

This way they can be reused independently of updates like
getDerivedStateFromProps. This will be important for resuming.

* Revert back to storing hasForceUpdate on the update queue

Instead of using the effect tag. Ideally, this would be part of the
return type of processUpdateQueue.

* Rename UpdateQueue effect type back to Callback

I don't love this name either, but it's less confusing than UpdateQueue
I suppose. Conceptually, this is usually a callback: setState callbacks,
componentDidCatch. The only case that feels a bit weird is Timeouts,
which use this effect to attach a promise listener. I guess that kinda
fits, too.

* Call getDerivedStateFromProps every render, even if props did not change

Rather than enqueue a new setState updater for every props change, we
can skip the update queue entirely and merge the result into state at
the end. This makes more sense, since "receiving props" is not an event
that should be observed. It's still a bit weird, since eventually we do
persist the derived state (in other words, it accumulates).

* Store captured effects on separate list from "own" effects (callbacks)

For resuming, we need the ability to discard the "own" effects while
reusing the captured effects.

* Optimize for class components

Change `process` and `callback` to match the expected payload types
for class components. I had intended for the update queue to be reusable
for both class components and a future React API, but we'll likely have
to fork anyway.

* Only double-invoke render phase lifecycles functions in DEV

* Use global state to track currently processing queue in DEV
mrizwanashiq pushed a commit to mrizwanashiq/react that referenced this pull request Jun 25, 2026
…ed (react#12802)

Fixes an oversight from react#12600. getDerivedStateFromProps should fire
if either props *or* state have changed, but not if *neither* have
changed. This prevents a parent from re-rendering if a deep child
receives an update.
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.

8 participants