Skip to content

Commit 724ba8e

Browse files
committed
refactor: move or create keyed node
1 parent 31fcfba commit 724ba8e

File tree

1 file changed

+113
-94
lines changed

1 file changed

+113
-94
lines changed

packages/qwik/src/core/client/vnode-diff.ts

Lines changed: 113 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -723,32 +723,12 @@ export const vnode_diff = (
723723
let needsQDispatchEventPatch = false;
724724
const currentKey = getKey(vCurrent);
725725
if (!isSameElementName || jsxKey !== currentKey) {
726-
// scan until you find the key you want.
727-
vNewNode = retrieveChildWithKey(elementName, jsxKey);
728-
// If found remove everything before and place in side buffer.
729-
if (vNewNode) {
730-
vCurrent = vNewNode;
731-
vNewNode = null;
732-
} else {
733-
// If not found, check the side buffer
734-
vNewNode = vSideBuffer?.get(elementName + ':' + jsxKey) || null;
735-
if (vNewNode) {
736-
vSideBuffer!.delete(elementName + ':' + jsxKey);
737-
// if found insert from side-buffer
738-
// Existing keyed node
739-
vnode_insertBefore(journal, vParent as ElementVNode, vNewNode, vCurrent);
740-
// We are here, so jsx is different from the vCurrent, so now we want to point to the moved node.
741-
vCurrent = vNewNode;
742-
// We need to clean up the vNewNode, because we don't want to skip advance to next sibling (see `advance` function).
743-
vNewNode = null;
744-
} else {
745-
// if not found it is a new item create it.
746-
needsQDispatchEventPatch = createNewElement(jsx, elementName);
747-
}
748-
}
749-
} else if (vSideBuffer?.has(elementName + ':' + jsxKey)) {
726+
const sideBufferKey = getSideBufferKey(elementName, jsxKey);
727+
const createNew = () => (needsQDispatchEventPatch = createNewElement(jsx, elementName));
728+
moveOrCreateKeyedNode(elementName, jsxKey, sideBufferKey, vParent as ElementVNode, createNew);
729+
} else {
750730
// delete the key from the side buffer if it is the same element
751-
vSideBuffer.delete(elementName + ':' + jsxKey);
731+
deleteFromSideBuffer(elementName, jsxKey);
752732
}
753733

754734
// reconcile attributes
@@ -1039,14 +1019,89 @@ export const vnode_diff = (
10391019
const name = vnode_isElementVNode(vCurrent) ? vnode_getElementName(vCurrent) : null;
10401020
const vKey = getKey(vCurrent) || getComponentHash(vCurrent, container.$getObjectById$);
10411021
if (vKey != null) {
1042-
const sideBufferKey = name ? name + ':' + vKey : vKey;
1022+
const sideBufferKey = getSideBufferKey(name, vKey);
10431023
vSideBuffer ||= new Map();
10441024
vSideBuffer.set(sideBufferKey, vCurrent);
10451025
}
10461026
}
10471027
return vNodeWithKey;
10481028
}
10491029

1030+
function getSideBufferKey(nodeName: string | null, key: string): string;
1031+
function getSideBufferKey(nodeName: string | null, key: string | null): string | null;
1032+
function getSideBufferKey(nodeName: string | null, key: string | null): string | null {
1033+
if (key == null) {
1034+
return null;
1035+
}
1036+
return nodeName ? nodeName + ':' + key : key;
1037+
}
1038+
1039+
function deleteFromSideBuffer(nodeName: string | null, key: string | null): boolean {
1040+
const sbKey = getSideBufferKey(nodeName, key);
1041+
if (sbKey && vSideBuffer?.has(sbKey)) {
1042+
vSideBuffer.delete(sbKey);
1043+
return true;
1044+
}
1045+
return false;
1046+
}
1047+
1048+
/**
1049+
* Shared utility to resolve a keyed node by:
1050+
*
1051+
* 1. Scanning forward siblings via `retrieveChildWithKey`
1052+
* 2. Falling back to the side buffer using the provided `sideBufferKey`
1053+
* 3. Creating a new node via `createNew` when not found
1054+
*
1055+
* If a node is moved from the side buffer, it is inserted before `vCurrent` under
1056+
* `parentForInsert`. The function updates `vCurrent`/`vNewNode` accordingly and returns the value
1057+
* from `createNew` when a new node is created.
1058+
*/
1059+
function moveOrCreateKeyedNode(
1060+
nodeName: string | null,
1061+
lookupKey: string | null,
1062+
sideBufferKey: string | null,
1063+
parentForInsert: VNode,
1064+
createNew: () => any,
1065+
addCurrentToSideBufferOnSideInsert?: boolean
1066+
): any {
1067+
// 1) Try to find the node among upcoming siblings
1068+
vNewNode = retrieveChildWithKey(nodeName, lookupKey);
1069+
if (vNewNode) {
1070+
vCurrent = vNewNode;
1071+
vNewNode = null;
1072+
return;
1073+
}
1074+
1075+
// 2) Try side buffer
1076+
if (sideBufferKey != null) {
1077+
const buffered = vSideBuffer?.get(sideBufferKey) || null;
1078+
if (buffered) {
1079+
vSideBuffer!.delete(sideBufferKey);
1080+
if (addCurrentToSideBufferOnSideInsert && vCurrent) {
1081+
const currentKey =
1082+
getKey(vCurrent) || getComponentHash(vCurrent, container.$getObjectById$);
1083+
if (currentKey != null) {
1084+
const currentName = vnode_isElementVNode(vCurrent)
1085+
? vnode_getElementName(vCurrent)
1086+
: null;
1087+
const currentSideKey = getSideBufferKey(currentName, currentKey);
1088+
if (currentSideKey != null) {
1089+
vSideBuffer ||= new Map();
1090+
vSideBuffer.set(currentSideKey, vCurrent);
1091+
}
1092+
}
1093+
}
1094+
vnode_insertBefore(journal, parentForInsert as any, buffered, vCurrent);
1095+
vCurrent = buffered;
1096+
vNewNode = null;
1097+
return;
1098+
}
1099+
}
1100+
1101+
// 3) Create new
1102+
return createNew();
1103+
}
1104+
10501105
function expectVirtual(type: VirtualType, jsxKey: string | null) {
10511106
const checkKey = type === VirtualType.Fragment;
10521107
const currentKey = getKey(vCurrent);
@@ -1058,33 +1113,11 @@ export const vnode_diff = (
10581113

10591114
if (isSameNode) {
10601115
// All is good.
1061-
if (currentKey && vSideBuffer?.has(currentKey)) {
1062-
vSideBuffer.delete(currentKey);
1063-
}
1116+
deleteFromSideBuffer(null, currentKey);
10641117
return;
10651118
}
1066-
if (jsxKey !== null) {
1067-
// Try to find the node.
1068-
vNewNode = retrieveChildWithKey(null, jsxKey);
1069-
if (vNewNode) {
1070-
vCurrent = vNewNode;
1071-
vNewNode = null;
1072-
} else {
1073-
// If not found, check the side buffer
1074-
vNewNode = vSideBuffer?.get(jsxKey) || null;
1075-
if (vNewNode) {
1076-
// if found insert from side-buffer
1077-
vSideBuffer!.delete(jsxKey);
1078-
// Add current to the side buffer
1079-
if (vCurrent && currentKey) {
1080-
vSideBuffer?.set(currentKey, vCurrent);
1081-
}
1082-
vnode_insertBefore(journal, vParent as VirtualVNode, vNewNode, vCurrent);
1083-
}
1084-
}
1085-
}
1086-
if (vNewNode === null) {
1087-
// if not found it is a new item create it.
1119+
1120+
const createNew = () => {
10881121
vnode_insertBefore(
10891122
journal,
10901123
vParent as VirtualVNode,
@@ -1093,7 +1126,20 @@ export const vnode_diff = (
10931126
);
10941127
(vNewNode as VirtualVNode).setProp(ELEMENT_KEY, jsxKey);
10951128
isDev && (vNewNode as VirtualVNode).setProp(DEBUG_TYPE, type);
1129+
};
1130+
// For fragments without a key, always create a new virtual node (ensures rerender semantics)
1131+
if (checkKey && jsxKey === null) {
1132+
createNew();
1133+
return;
10961134
}
1135+
moveOrCreateKeyedNode(
1136+
null,
1137+
jsxKey,
1138+
getSideBufferKey(null, jsxKey),
1139+
vParent as VirtualVNode,
1140+
createNew,
1141+
true
1142+
);
10971143
}
10981144

10991145
function expectComponent(component: Function) {
@@ -1116,32 +1162,19 @@ export const vnode_diff = (
11161162
const hashesAreEqual = componentHash === vNodeComponentHash;
11171163

11181164
if (!lookupKeysAreEqual) {
1119-
vNewNode = retrieveChildWithKey(null, lookupKey);
1120-
if (vNewNode) {
1121-
vCurrent = vNewNode;
1122-
vNewNode = null;
1123-
host = vCurrent as VirtualVNode;
1124-
} else {
1125-
vNewNode = lookupKey != null ? vSideBuffer?.get(lookupKey) || null : null;
1126-
if (vNewNode) {
1127-
vSideBuffer!.delete(lookupKey);
1128-
vnode_insertBefore(journal, vParent as VirtualVNode, vNewNode, vCurrent);
1129-
vCurrent = vNewNode;
1130-
vNewNode = null;
1131-
host = vCurrent as VirtualVNode;
1132-
} else {
1133-
insertNewComponent(host, componentQRL, jsxProps);
1134-
shouldRender = true;
1135-
host = vNewNode! as VirtualVNode;
1136-
}
1137-
}
1165+
const createNew = () => {
1166+
insertNewComponent(host, componentQRL, jsxProps);
1167+
shouldRender = true;
1168+
host = vNewNode! as VirtualVNode;
1169+
};
1170+
moveOrCreateKeyedNode(null, lookupKey, lookupKey, vParent as VirtualVNode, createNew);
11381171
} else if (!hashesAreEqual || !jsxNode.key) {
11391172
insertNewComponent(host, componentQRL, jsxProps);
11401173
host = vNewNode as VirtualVNode;
11411174
shouldRender = true;
1142-
} else if (vSideBuffer?.has(lookupKey)) {
1175+
} else {
11431176
// delete the key from the side buffer if it is the same component
1144-
vSideBuffer.delete(lookupKey);
1177+
deleteFromSideBuffer(null, lookupKey);
11451178
}
11461179

11471180
if (host) {
@@ -1196,29 +1229,15 @@ export const vnode_diff = (
11961229
insertNewInlineComponent();
11971230
host = vNewNode as VirtualVNode;
11981231
} else if (!lookupKeysAreEqual) {
1199-
// See if we already have this inline component later on.
1200-
vNewNode = retrieveChildWithKey(null, lookupKey);
1201-
if (vNewNode) {
1202-
vCurrent = vNewNode;
1203-
vNewNode = null;
1204-
host = vCurrent as VirtualVNode;
1205-
} else {
1206-
vNewNode = vSideBuffer?.get(lookupKey) || null;
1207-
if (vNewNode) {
1208-
vSideBuffer!.delete(lookupKey);
1209-
vnode_insertBefore(journal, vParent as VirtualVNode, vNewNode, vCurrent);
1210-
vCurrent = vNewNode;
1211-
vNewNode = null;
1212-
host = vCurrent as VirtualVNode;
1213-
} else {
1214-
// We did not find the inline component, create it.
1215-
insertNewInlineComponent();
1216-
host = vNewNode! as VirtualVNode;
1217-
}
1218-
}
1219-
} else if (vSideBuffer?.has(lookupKey)) {
1232+
const createNew = () => {
1233+
// We did not find the inline component, create it.
1234+
insertNewInlineComponent();
1235+
host = vNewNode! as VirtualVNode;
1236+
};
1237+
moveOrCreateKeyedNode(null, lookupKey, lookupKey, vParent as VirtualVNode, createNew);
1238+
} else {
12201239
// delete the key from the side buffer if it is the same component
1221-
vSideBuffer.delete(lookupKey);
1240+
deleteFromSideBuffer(null, lookupKey);
12221241
}
12231242

12241243
if (host) {

0 commit comments

Comments
 (0)