From: daiwei Date: Tue, 29 Jul 2025 10:21:12 +0000 (+0800) Subject: fix(hydration): handle v-if on insertion parent X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9d3ee8e2ec66bdcee341b8c6c105d5ab37985e44;p=thirdparty%2Fvuejs%2Fcore.git fix(hydration): handle v-if on insertion parent --- diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index 0c0919ae40..54381d7e68 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -1134,6 +1134,28 @@ describe('Vapor Mode hydration', () => { expect(container.innerHTML).toBe(`
foo
`) }) + test('v-if on insertion parent', async () => { + const data = ref(true) + const { container } = await testHydration( + ``, + { Child: `` }, + data, + ) + expect(container.innerHTML).toBe(`
foo
`) + + data.value = false + await nextTick() + expect(container.innerHTML).toBe(``) + + data.value = true + await nextTick() + expect(container.innerHTML).toBe(`
foo
`) + }) + test('v-if/else-if/else chain - switch branches', async () => { const data = ref('a') const { container } = await testHydration( diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts index 56b9d9b4a2..bfa9e6e7af 100644 --- a/packages/runtime-vapor/src/apiCreateIf.ts +++ b/packages/runtime-vapor/src/apiCreateIf.ts @@ -9,6 +9,37 @@ import { import { renderEffect } from './renderEffect' import { DynamicFragment } from './fragment' +const ifStack = [] as DynamicFragment[] +const insertionParents = new WeakMap() + +/** + * Collects insertionParents inside an if block during hydration + * When the if condition becomes false on the client, clears the + * HTML of these insertionParents to prevent duplicate rendering + * results when the condition becomes true again + * + * Example: + * const t2 = _template("
") + * const n2 = _createIf(() => show.value, () => { + * const n5 = t2() + * _setInsertionState(n5) + * const n4 = _createComponent(Comp) // renders `` + * return n5 + * }) + * + * After hydration, the HTML of `n5` is `
` instead of `
`. + * When `show.value` becomes false, the HTML of `n5` needs to be cleared, + * to avoid duplicated rendering when `show.value` becomes true again. + */ +export function collectInsertionParents(insertionParent: ParentNode): void { + const currentIf = ifStack[ifStack.length - 1] + if (currentIf) { + let nodes = insertionParents.get(currentIf) + if (!nodes) insertionParents.set(currentIf, (nodes = [])) + nodes.push(insertionParent) + } +} + export function createIf( condition: () => any, b1: BlockFn, @@ -27,7 +58,19 @@ export function createIf( isHydrating || __DEV__ ? new DynamicFragment(IF_ANCHOR_LABEL) : new DynamicFragment() + if (isHydrating) { + ;(frag as DynamicFragment).teardown = () => { + const nodes = insertionParents.get(frag as DynamicFragment) + if (nodes) { + nodes.forEach(p => ((p as Element).innerHTML = '')) + insertionParents.delete(frag as DynamicFragment) + } + ;(frag as DynamicFragment).teardown = undefined + } + ifStack.push(frag as DynamicFragment) + } renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2)) + isHydrating && ifStack.pop() } if (!isHydrating && _insertionParent) { diff --git a/packages/runtime-vapor/src/fragment.ts b/packages/runtime-vapor/src/fragment.ts index 9f51fac382..50ce18bdb3 100644 --- a/packages/runtime-vapor/src/fragment.ts +++ b/packages/runtime-vapor/src/fragment.ts @@ -59,6 +59,7 @@ export class DynamicFragment extends VaporFragment { * indicates forwarded slot */ forwarded?: boolean + teardown?: () => void constructor(anchorLabel?: string) { super([]) @@ -97,6 +98,7 @@ export class DynamicFragment extends VaporFragment { // teardown previous branch if (this.scope) { this.scope.stop() + if (parent) this.teardown && this.teardown() const mode = transition && transition.mode if (mode) { applyTransitionLeaveHooks(this.nodes, transition, renderBranch) diff --git a/packages/runtime-vapor/src/insertionState.ts b/packages/runtime-vapor/src/insertionState.ts index 5c4c41fe23..5c8ba4262e 100644 --- a/packages/runtime-vapor/src/insertionState.ts +++ b/packages/runtime-vapor/src/insertionState.ts @@ -1,3 +1,6 @@ +import { collectInsertionParents } from './apiCreateIf' +import { isHydrating } from './dom/hydration' + export let insertionParent: | (ParentNode & { // dynamic node position - hydration only @@ -21,6 +24,10 @@ export let insertionAnchor: Node | 0 | undefined export function setInsertionState(parent: ParentNode, anchor?: Node | 0): void { insertionParent = parent insertionAnchor = anchor + + if (isHydrating) { + collectInsertionParents(parent) + } } export function resetInsertionState(): void {