mirror of https://github.com/vuejs/core.git
fix(hydration): handle v-if on insertion parent
This commit is contained in:
parent
6505a8f155
commit
9d3ee8e2ec
|
@ -1134,6 +1134,28 @@ describe('Vapor Mode hydration', () => {
|
||||||
expect(container.innerHTML).toBe(`<div>foo</div><!---->`)
|
expect(container.innerHTML).toBe(`<div>foo</div><!---->`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('v-if on insertion parent', async () => {
|
||||||
|
const data = ref(true)
|
||||||
|
const { container } = await testHydration(
|
||||||
|
`<template>
|
||||||
|
<div v-if="data">
|
||||||
|
<components.Child/>
|
||||||
|
</div>
|
||||||
|
</template>`,
|
||||||
|
{ Child: `<template>foo</template>` },
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
|
||||||
|
|
||||||
|
data.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
|
||||||
|
|
||||||
|
data.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
|
||||||
|
})
|
||||||
|
|
||||||
test('v-if/else-if/else chain - switch branches', async () => {
|
test('v-if/else-if/else chain - switch branches', async () => {
|
||||||
const data = ref('a')
|
const data = ref('a')
|
||||||
const { container } = await testHydration(
|
const { container } = await testHydration(
|
||||||
|
|
|
@ -9,6 +9,37 @@ import {
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
import { DynamicFragment } from './fragment'
|
import { DynamicFragment } from './fragment'
|
||||||
|
|
||||||
|
const ifStack = [] as DynamicFragment[]
|
||||||
|
const insertionParents = new WeakMap<DynamicFragment, Node[]>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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("<div></div>")
|
||||||
|
* const n2 = _createIf(() => show.value, () => {
|
||||||
|
* const n5 = t2()
|
||||||
|
* _setInsertionState(n5)
|
||||||
|
* const n4 = _createComponent(Comp) // renders `<span></span>`
|
||||||
|
* return n5
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* After hydration, the HTML of `n5` is `<div><span></span></div>` instead of `<div></div>`.
|
||||||
|
* 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(
|
export function createIf(
|
||||||
condition: () => any,
|
condition: () => any,
|
||||||
b1: BlockFn,
|
b1: BlockFn,
|
||||||
|
@ -27,7 +58,19 @@ export function createIf(
|
||||||
isHydrating || __DEV__
|
isHydrating || __DEV__
|
||||||
? new DynamicFragment(IF_ANCHOR_LABEL)
|
? new DynamicFragment(IF_ANCHOR_LABEL)
|
||||||
: new DynamicFragment()
|
: 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))
|
renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2))
|
||||||
|
isHydrating && ifStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isHydrating && _insertionParent) {
|
if (!isHydrating && _insertionParent) {
|
||||||
|
|
|
@ -59,6 +59,7 @@ export class DynamicFragment extends VaporFragment {
|
||||||
* indicates forwarded slot
|
* indicates forwarded slot
|
||||||
*/
|
*/
|
||||||
forwarded?: boolean
|
forwarded?: boolean
|
||||||
|
teardown?: () => void
|
||||||
|
|
||||||
constructor(anchorLabel?: string) {
|
constructor(anchorLabel?: string) {
|
||||||
super([])
|
super([])
|
||||||
|
@ -97,6 +98,7 @@ export class DynamicFragment extends VaporFragment {
|
||||||
// teardown previous branch
|
// teardown previous branch
|
||||||
if (this.scope) {
|
if (this.scope) {
|
||||||
this.scope.stop()
|
this.scope.stop()
|
||||||
|
if (parent) this.teardown && this.teardown()
|
||||||
const mode = transition && transition.mode
|
const mode = transition && transition.mode
|
||||||
if (mode) {
|
if (mode) {
|
||||||
applyTransitionLeaveHooks(this.nodes, transition, renderBranch)
|
applyTransitionLeaveHooks(this.nodes, transition, renderBranch)
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { collectInsertionParents } from './apiCreateIf'
|
||||||
|
import { isHydrating } from './dom/hydration'
|
||||||
|
|
||||||
export let insertionParent:
|
export let insertionParent:
|
||||||
| (ParentNode & {
|
| (ParentNode & {
|
||||||
// dynamic node position - hydration only
|
// dynamic node position - hydration only
|
||||||
|
@ -21,6 +24,10 @@ export let insertionAnchor: Node | 0 | undefined
|
||||||
export function setInsertionState(parent: ParentNode, anchor?: Node | 0): void {
|
export function setInsertionState(parent: ParentNode, anchor?: Node | 0): void {
|
||||||
insertionParent = parent
|
insertionParent = parent
|
||||||
insertionAnchor = anchor
|
insertionAnchor = anchor
|
||||||
|
|
||||||
|
if (isHydrating) {
|
||||||
|
collectInsertionParents(parent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetInsertionState(): void {
|
export function resetInsertionState(): void {
|
||||||
|
|
Loading…
Reference in New Issue