Skip to content

Commit ffb9f5c

Browse files
committed
enhancement: Refine Chimera safeguard in VDom.syncVdomState (#8598)
Changed syncVdomState to continue recursing into children even if the root tags mismatch. This ensures child nodes can synchronize their IDs (and thus be preserved/moved) when a parent tag is changed (e.g. div->ul). Property synchronization (ID, scroll) is skipped for the mismatched node itself to prevent 'Chimera' corruption.
1 parent 4ac2da8 commit ffb9f5c

1 file changed

Lines changed: 32 additions & 31 deletions

File tree

src/util/VDom.mjs

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -429,46 +429,47 @@ class VDom extends Base {
429429
if (vnode && vdom) {
430430
// Sanity check: If the node types (tags) mismatch, we are likely looking at
431431
// a race condition where the VNode tree structure hasn't caught up with the VDOM yet.
432-
// In this case, we must abort synchronization to prevent "Chimera" nodes (e.g. UL getting LI ID).
433-
if (vnode.nodeName && vdom.tag && vnode.nodeName.toLowerCase() !== vdom.tag.toLowerCase()) {
434-
return
435-
}
432+
// In this case, we do not sync the node props, but we do want to check the children.
433+
// This is important for e.g. tag name changes (div => ul), where we want to keep the children stable.
434+
const tagMismatch = vnode.nodeName && vdom.tag && vnode.nodeName.toLowerCase() !== vdom.tag.toLowerCase();
436435

437436
vdom = VDom.getVdom(vdom);
438437

439438
let childNodes = vdom.cn,
440439
cn, i, len;
441440

442-
if (force) {
443-
if (vnode.id && vdom.id !== vnode.id) {
444-
vdom.id = vnode.id
445-
}
446-
} else {
447-
// We only want to add an ID if the vdom node does not already have one.
448-
// This preserves developer-provided IDs while allowing the framework
449-
// to assign IDs to nodes that need them for reconciliation.
450-
// Also think of adding and removing nodes in parallel.
451-
if (vnode.id && (!vdom.id || vdom.id.startsWith('neo-vnode-'))) {
452-
vdom.id = vnode.id
441+
if (!tagMismatch) {
442+
if (force) {
443+
if (vnode.id && vdom.id !== vnode.id) {
444+
vdom.id = vnode.id
445+
}
446+
} else {
447+
// We only want to add an ID if the vdom node does not already have one.
448+
// This preserves developer-provided IDs while allowing the framework
449+
// to assign IDs to nodes that need them for reconciliation.
450+
// Also think of adding and removing nodes in parallel.
451+
if (vnode.id && (!vdom.id || vdom.id.startsWith('neo-vnode-'))) {
452+
vdom.id = vnode.id
453+
}
453454
}
454-
}
455455

456-
// 1. Rehydration (vnode -> vdom)
457-
// Used by Functional Components (vdom is new)
458-
if (Neo.isNumber(vnode.scrollTop) && !Neo.isNumber(vdom.scrollTop)) {
459-
vdom.scrollTop = vnode.scrollTop
460-
}
461-
if (Neo.isNumber(vnode.scrollLeft) && !Neo.isNumber(vdom.scrollLeft)) {
462-
vdom.scrollLeft = vnode.scrollLeft
463-
}
456+
// 1. Rehydration (vnode -> vdom)
457+
// Used by Functional Components (vdom is new)
458+
if (Neo.isNumber(vnode.scrollTop) && !Neo.isNumber(vdom.scrollTop)) {
459+
vdom.scrollTop = vnode.scrollTop
460+
}
461+
if (Neo.isNumber(vnode.scrollLeft) && !Neo.isNumber(vdom.scrollLeft)) {
462+
vdom.scrollLeft = vnode.scrollLeft
463+
}
464464

465-
// 2. Preservation (vdom -> vnode)
466-
// Used by Classic Components (vdom is source of truth via capture)
467-
if (Neo.isNumber(vdom.scrollTop)) {
468-
vnode.scrollTop = vdom.scrollTop
469-
}
470-
if (Neo.isNumber(vdom.scrollLeft)) {
471-
vnode.scrollLeft = vdom.scrollLeft
465+
// 2. Preservation (vdom -> vnode)
466+
// Used by Classic Components (vdom is source of truth via capture)
467+
if (Neo.isNumber(vdom.scrollTop)) {
468+
vnode.scrollTop = vdom.scrollTop
469+
}
470+
if (Neo.isNumber(vdom.scrollLeft)) {
471+
vnode.scrollLeft = vdom.scrollLeft
472+
}
472473
}
473474

474475
if (childNodes) {

0 commit comments

Comments
 (0)