Skip to content

Re-purpose renderCallbacks as a general per-component commit ph…#2011

Merged
marvinhagemeister merged 12 commits into
masterfrom
feat/commit-queue
Oct 18, 2019
Merged

Re-purpose renderCallbacks as a general per-component commit ph…#2011
marvinhagemeister merged 12 commits into
masterfrom
feat/commit-queue

Conversation

@andrewiggins

Copy link
Copy Markdown
Member

Currently the only lifecycle method that is invoked after all work for a diff is done is componentDidMount. This PR re-purposes the _renderCallbacks queue in each component to be a general "after work" or "commit phase" callback queue. I use this queue to queue up several kinds of methods:

  1. componentDidMount
  2. componentDidUpdate
  3. setState callbacks
  4. forceUpdate callbacks

I believe all of these methods are intended to be invoked after a diff is completed and all dom work is done. This PR makes that true! And the best part, only for 11B!

I'm hoping that we can further reuse this queue for the useLayoutEffect hook since this hook is basically just a way to directly "hook" into this mechanism (running a callback synchronously after a diff is complete), but that'll come in another PR.

I included test from #884 (which previously tried to add this behavior for cDU) and fixed the test from #506 to verify the changes.

Fixes #648
Fixes #1223

@coveralls

coveralls commented Oct 15, 2019

Copy link
Copy Markdown

Coverage Status

Coverage increased (+0.1%) to 99.769% when pulling 46b1ad1 on feat/commit-queue into b589dec on master.

Comment thread src/component.js
let mounts = [];
let newDom = diff(parentDom, vnode, assign({}, vnode), component._context, parentDom.ownerSVGElement!==undefined, null, mounts, oldDom == null ? getDomSibling(vnode) : oldDom);
commitRoot(mounts, vnode);
let commitQueue = [];

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Do people like this rename of mounts to commitQueue? Since the mounts array isn't just used for componentDidMount anymore I figured it probably needed a new name that better communicated that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+1 for the name change. It better reflects the actual thing it's used for 👍

Comment thread src/diff/index.js
oldState = c.state;
if (c.componentDidUpdate!=null) {
c._renderCallbacks.push(() => {
c.componentDidUpdate(oldProps, oldState, snapshot);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Note, I've moved the queuing of cDU up here to reuse the isNew check and to also no longer need to check oldProps == null as a way to determine if sCU returned false. Plus it lives with the rest of the lifecycle methods so I think reasoning about lifecycle methods will be a little easier.

Comment thread src/diff/index.js
c.componentDidMount();
commitQueue = c._renderCallbacks;
c._renderCallbacks = [];
commitQueue.some(cb => { cb.call(c); });

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@developit, it seems back in #648, you expressed perf concerns about a general callback queue, which I think is what I've implemented here. Any concerns with this implementation?

this.state = { i: 0 };
}

componentDidMount() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Syncrhonously calling rerender inside of cDM doesn't effectively replicate how Preact's diffing algorithm works (we batch updates and diff them later). Plus it causes an infinite loop so I rewrote this test.

Comment thread test/browser/keys.test.js
render(<Foo moved={false} />, scratch);

expect(ops).to.deep.equal(['Mount Stateful2', 'Mount Stateful1']);
expect(ops).to.deep.equal(['Mount Stateful1', 'Mount Stateful2']);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Previously Preact called cDM for siblings in reverse order. Now we call cDM for siblings from top to bottom. Since cDM is invoked after all DOM operations are done, I don't think this order matters as much, but for what its worth, our order matches React now (codesandbox).

This sibling re-ordering also impacted the should handle hoisted component vnodes without DOM test.

@marvinhagemeister marvinhagemeister left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hero 🙌💯 I have a feeling like this PR will resolve even more compatibility issues than we're aware of. Iirc a few users running into the ordering issue when it comes to lifecycles. Super excited about landing this PR👍🍀

@JoviDeCroock JoviDeCroock left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very excited about this, concern: componentDidUpdate will now fire when a component is unmounted + props updated, no?

@andrewiggins

Copy link
Copy Markdown
Member Author

Thanks! 😄

@JoviDeCroock I think cDU always fired when props change (see this lifecycle test). And unmounted components don't go through diff when they are unmounted so cDU won't get invoked when unmounting (relevant lifecycle test). So I think this PR maintains the current behavior.

@JoviDeCroock JoviDeCroock left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great work Andre! 👍

@marvinhagemeister marvinhagemeister changed the title Re-purpose renderCallbacks as a general per-component commit phase callback queue (+11 B) Re-purpose renderCallbacks as a general per-component commit ph… Oct 18, 2019
@marvinhagemeister marvinhagemeister merged commit e22cc99 into master Oct 18, 2019
@marvinhagemeister marvinhagemeister deleted the feat/commit-queue branch October 18, 2019 06:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

componentDidMount() hooks of child are called after componentDidUpdate() hook of the parent Post-mount with componentDidMount

4 participants