Skip to content

Commit

Permalink
Always clear _nextDom field on VNodes (#4166)
Browse files Browse the repository at this point in the history
* Always clear _nextDom

* Remove reference to _nextDom in Suspense

_nextDom was always null/undefined in our test suite in this code path so replacing it with a direct call to appendChild.

* Clear render root _nextDom in commitRoot

* improve types in diffChildren

* Undo variable order change

Locally improves perf??
  • Loading branch information
andrewiggins committed Oct 24, 2023
1 parent e24fdad commit e6edf13
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 17 deletions.
2 changes: 1 addition & 1 deletion compat/src/suspense.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function removeOriginal(vnode, detachedParent, originalParent) {
if (vnode._component) {
if (vnode._component._parentDom === detachedParent) {
if (vnode._dom) {
originalParent.insertBefore(vnode._dom, vnode._nextDom);
originalParent.appendChild(vnode._dom);
}
vnode._component._force = true;
vnode._component._parentDom = originalParent;
Expand Down
32 changes: 16 additions & 16 deletions src/diff/children.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,28 +196,28 @@ export function diffChildren(

skewedIndex = i + skew;

if (
typeof childVNode.type == 'function' &&
(matchingIndex !== skewedIndex ||
oldVNode._children === childVNode._children)
) {
oldDom = reorderChildren(childVNode, oldDom, parentDom);
} else if (
typeof childVNode.type != 'function' &&
(matchingIndex !== skewedIndex || isMounting)
) {
oldDom = placeChild(parentDom, newDom, oldDom);
} else if (childVNode._nextDom !== undefined) {
// Only Fragments or components that return Fragment like VNodes will
// have a non-undefined _nextDom. Continue the diff from the sibling
// of last DOM child of this child VNode
oldDom = childVNode._nextDom;
if (typeof childVNode.type == 'function') {
if (
matchingIndex !== skewedIndex ||
oldVNode._children === childVNode._children
) {
oldDom = reorderChildren(childVNode, oldDom, parentDom);
} else if (childVNode._nextDom !== undefined) {
// Only Fragments or components that return Fragment like VNodes will
// have a non-undefined _nextDom. Continue the diff from the sibling
// of last DOM child of this child VNode
oldDom = childVNode._nextDom;
} else {
oldDom = newDom.nextSibling;
}

// Eagerly cleanup _nextDom. We don't need to persist the value because
// it is only used by `diffChildren` to determine where to resume the diff after
// diffing Components and Fragments. Once we store it the nextDOM local var, we
// can clean up the property
childVNode._nextDom = undefined;
} else if (matchingIndex !== skewedIndex || isMounting) {
oldDom = placeChild(parentDom, newDom, oldDom);
} else {
oldDom = newDom.nextSibling;
}
Expand Down
2 changes: 2 additions & 0 deletions src/diff/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ export function diff(
* @param {import('../internal').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
23 changes: 23 additions & 0 deletions test/_util/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,14 @@ export function clearOptions() {
* @param {HTMLElement} scratch
*/
export function teardown(scratch) {
if (
scratch &&
('__k' in scratch || '_children' in scratch) &&
scratch._children
) {
verifyVNodeTree(scratch._children);
}

if (scratch) {
scratch.parentNode.removeChild(scratch);
}
Expand All @@ -226,6 +234,21 @@ export function teardown(scratch) {
restoreElementAttributes();
}

/** @type {(vnode: import('../../src/internal').VNode) => void} */
function verifyVNodeTree(vnode) {
if (vnode._nextDom) {
expect.fail('vnode should not have _nextDom:' + vnode._nextDom);
}

if (vnode._children) {
for (let child of vnode._children) {
if (child) {
verifyVNodeTree(child);
}
}
}
}

const Foo = () => 'd';
export const getMixedArray = () =>
// Make it a function so each test gets a new copy of the array
Expand Down

0 comments on commit e6edf13

Please sign in to comment.