Skip to content

Commit 31632a2

Browse files
authored
Do not re-insert memoized vnodes that keep their relative order after swap (#4888)
1 parent 61f47ca commit 31632a2

File tree

2 files changed

+69
-11
lines changed

2 files changed

+69
-11
lines changed

src/diff/children.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,9 @@ export function diffChildren(
128128
firstChildDom = newDom;
129129
}
130130

131-
if (
132-
childVNode._flags & INSERT_VNODE ||
133-
oldVNode._children === childVNode._children
134-
) {
135-
oldDom = insert(childVNode, oldDom, parentDom);
131+
let shouldPlace = !!(childVNode._flags & INSERT_VNODE);
132+
if (shouldPlace || oldVNode._children === childVNode._children) {
133+
oldDom = insert(childVNode, oldDom, parentDom, shouldPlace);
136134
} else if (typeof childVNode.type == 'function' && result !== UNDEFINED) {
137135
oldDom = result;
138136
} else if (newDom) {
@@ -342,9 +340,10 @@ function constructNewChildrenArray(
342340
* @param {VNode} parentVNode
343341
* @param {PreactElement} oldDom
344342
* @param {PreactElement} parentDom
343+
* @param {boolean} shouldPlace
345344
* @returns {PreactElement}
346345
*/
347-
function insert(parentVNode, oldDom, parentDom) {
346+
function insert(parentVNode, oldDom, parentDom, shouldPlace) {
348347
// Note: VNodes in nested suspended trees may be missing _children.
349348
if (typeof parentVNode.type == 'function') {
350349
let children = parentVNode._children;
@@ -355,16 +354,18 @@ function insert(parentVNode, oldDom, parentDom) {
355354
// children's _parent pointer to point to the newVNode (parentVNode
356355
// here).
357356
children[i]._parent = parentVNode;
358-
oldDom = insert(children[i], oldDom, parentDom);
357+
oldDom = insert(children[i], oldDom, parentDom, shouldPlace);
359358
}
360359
}
361360

362361
return oldDom;
363362
} else if (parentVNode._dom != oldDom) {
364-
if (oldDom && parentVNode.type && !oldDom.parentNode) {
365-
oldDom = getDomSibling(parentVNode);
363+
if (shouldPlace) {
364+
if (oldDom && parentVNode.type && !oldDom.parentNode) {
365+
oldDom = getDomSibling(parentVNode);
366+
}
367+
parentDom.insertBefore(parentVNode._dom, oldDom || NULL);
366368
}
367-
parentDom.insertBefore(parentVNode._dom, oldDom || NULL);
368369
oldDom = parentVNode._dom;
369370
}
370371

test/browser/lifecycles/shouldComponentUpdate.test.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { setupRerender } from 'preact/test-utils';
22
import { createElement, render, Component, Fragment } from 'preact';
33
import { vi } from 'vitest';
44
import { setupScratch, teardown } from '../../_util/helpers';
5-
import { logCall, clearLog } from '../../_util/logCall';
5+
import { logCall, getLog, clearLog } from '../../_util/logCall';
66

77
/** @jsx createElement */
88

@@ -962,4 +962,61 @@ describe('Lifecycle methods', () => {
962962
`<div>Before</div><div>Component</div><div>After</div>`
963963
);
964964
});
965+
966+
it('should not re-insert memoized items that keep their relative order after swap', () => {
967+
class MemoizedItem extends Component {
968+
shouldComponentUpdate(nextProps) {
969+
return nextProps.value !== this.props.value;
970+
}
971+
render() {
972+
return <div>{this.props.value}</div>;
973+
}
974+
}
975+
976+
const App = ({ items }) => (
977+
<div>
978+
{items.map(value => (
979+
<MemoizedItem key={value} value={value} />
980+
))}
981+
</div>
982+
);
983+
984+
render(<App items={[1, 2, 3, 4, 5, 6, 7]} />, scratch);
985+
986+
function renderItemsAndAssert({ items, expectedLog }) {
987+
clearLog();
988+
render(<App items={items} />, scratch);
989+
expect(scratch.innerHTML).to.equal(
990+
`<div>${items.map(value => `<div>${value}</div>`).join('')}</div>`
991+
);
992+
expect(getLog()).to.deep.equal(expectedLog);
993+
}
994+
995+
// Swap 1 and 7
996+
renderItemsAndAssert({
997+
items: [7, 2, 3, 4, 5, 6, 1],
998+
expectedLog: [
999+
'<div>1234567.insertBefore(<div>7, <div>1)',
1000+
'<div>7123456.appendChild(<div>1)'
1001+
]
1002+
});
1003+
1004+
// Swap 2 and 6
1005+
renderItemsAndAssert({
1006+
items: [7, 6, 3, 4, 5, 2, 1],
1007+
expectedLog: [
1008+
'<div>7234561.insertBefore(<div>6, <div>2)',
1009+
'<div>7623451.insertBefore(<div>2, <div>1)'
1010+
]
1011+
});
1012+
1013+
// Swap 3 and 5
1014+
renderItemsAndAssert({
1015+
items: [7, 6, 5, 4, 3, 2, 1],
1016+
expectedLog: [
1017+
'<div>7634521.insertBefore(<div>5, <div>3)',
1018+
'<div>7653421.insertBefore(<div>3, <div>2)'
1019+
]
1020+
});
1021+
});
9651022
});

0 commit comments

Comments
 (0)