Skip to content

Commit

Permalink
Merge pull request preactjs#4234 from preactjs/multi-root-shared-commit
Browse files Browse the repository at this point in the history
WIP: batch commit callbacks from all components in the render queue
  • Loading branch information
developit authored Jan 5, 2024
2 parents e1f0d3e + b09f4c1 commit f7ccb90
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 15 deletions.
5 changes: 1 addition & 4 deletions compat/test/browser/useSyncExternalStore.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -658,10 +658,7 @@ describe('useSyncExternalStore', () => {
await act(() => {
store.set(1);
});
// Preact logs differ from React here cuz of how we do rerendering. We
// rerender subtrees and then commit effects so Child2 never sees the
// update to 1 cuz Child1 rerenders and runs its layout effects first.
assertLog([1, /*1,*/ 'Reset back to 0', 0, 0]);
assertLog([1, 1, 'Reset back to 0', 0, 0]);
expect(container.textContent).to.equal('00');
});

Expand Down
29 changes: 21 additions & 8 deletions src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { assign } from './util';
import { diff, commitRoot } from './diff/index';
import options from './options';
import { Fragment } from './create-element';
import { MODE_HYDRATE } from './constants';
import { EMPTY_ARR, MODE_HYDRATE } from './constants';

/**
* Base Component class. Provides `setState()` and `forceUpdate()`, which
Expand Down Expand Up @@ -120,12 +120,10 @@ export function getDomSibling(vnode, childIndex) {
* Trigger in-place re-rendering of a component.
* @param {Component} component The component to rerender
*/
function renderComponent(component) {
function renderComponent(component, commitQueue, refQueue) {
let oldVNode = component._vnode,
oldDom = oldVNode._dom,
parentDom = component._parentDom,
commitQueue = [],
refQueue = [];
parentDom = component._parentDom;

if (parentDom) {
const newVNode = assign({}, oldVNode);
Expand All @@ -146,11 +144,14 @@ function renderComponent(component) {
);

newVNode._parent._children[newVNode._index] = newVNode;
commitRoot(commitQueue, newVNode, refQueue);

newVNode._nextDom = undefined;

if (newVNode._dom != oldDom) {
updateParentDomPointers(newVNode);
}

return newVNode;
}
}

Expand Down Expand Up @@ -220,21 +221,33 @@ const depthSort = (a, b) => a._vnode._depth - b._vnode._depth;
/** Flush the render queue by rerendering all queued components */
function process() {
let c;
let commitQueue = [];
let refQueue = [];
let root;
rerenderQueue.sort(depthSort);
// Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary
// process() calls from getting scheduled while `queue` is still being consumed.
while ((c = rerenderQueue.shift())) {
if (c._dirty) {
let renderQueueLength = rerenderQueue.length;
renderComponent(c);
if (rerenderQueue.length > renderQueueLength) {
root = renderComponent(c, commitQueue, refQueue) || root;
// If this WAS the last component in the queue, run commit callbacks *before* we exit the tight loop.
// This is required in order for `componentDidMount(){this.setState()}` to be batched into one flush.
// Otherwise, also run commit callbacks if the render queue was mutated.
if (renderQueueLength === 0 || rerenderQueue.length > renderQueueLength) {
commitRoot(commitQueue, root, refQueue);
refQueue.length = commitQueue.length = 0;
root = undefined;
// When i.e. rerendering a provider additional new items can be injected, we want to
// keep the order from top to bottom with those new items so we can handle them in a
// single pass
rerenderQueue.sort(depthSort);
} else if (root) {
if (options._commit) options._commit(root, EMPTY_ARR);
}
}
}
if (root) commitRoot(commitQueue, root, refQueue);
process._rerenderCount = 0;
}

Expand Down
2 changes: 0 additions & 2 deletions src/diff/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,6 @@ export function diff(
* @param {VNode} root
*/
export function commitRoot(commitQueue, root, refQueue) {
root._nextDom = undefined;

for (let i = 0; i < refQueue.length; i++) {
applyRef(refQueue[i], refQueue[++i], refQueue[++i]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function render(vnode, parentDom, replaceNode) {
refQueue
);

// Flush all queued effects
vnode._nextDom = undefined;
commitRoot(commitQueue, vnode, refQueue);
}

Expand Down

0 comments on commit f7ccb90

Please sign in to comment.