fix(suspense): fix suspense patching in optimized mode

fix #3828
This commit is contained in:
Evan You 2021-05-27 16:32:31 -04:00
parent f0eb1978b2
commit 9f24195d2c
3 changed files with 74 additions and 29 deletions

View File

@ -23,6 +23,7 @@ import {
createApp
} from '@vue/runtime-test'
import { PatchFlags, SlotFlags } from '@vue/shared'
import { SuspenseImpl } from '../src/components/Suspense'
describe('renderer: optimized mode', () => {
let root: TestElement
@ -784,4 +785,40 @@ describe('renderer: optimized mode', () => {
await nextTick()
expect(inner(root)).toBe('<div><div><span>loading</span></div></div>')
})
// #3828
test('patch Suspense in optimized mode w/ nested dynamic nodes', async () => {
const show = ref(false)
const app = createApp({
render() {
return (
openBlock(),
createBlock(
Fragment,
null,
[
(openBlock(),
createBlock(SuspenseImpl, null, {
default: withCtx(() => [
createVNode('div', null, [
createVNode('div', null, show.value, PatchFlags.TEXT)
])
]),
_: SlotFlags.STABLE
}))
],
PatchFlags.STABLE_FRAGMENT
)
)
}
})
app.mount(root)
expect(inner(root)).toBe('<div><div>false</div></div>')
show.value = true
await nextTick()
expect(inner(root)).toBe('<div><div>true</div></div>')
})
})

View File

@ -1,9 +1,12 @@
import {
VNode,
normalizeVNode,
VNodeChild,
VNodeProps,
isSameVNodeType
isSameVNodeType,
openBlock,
closeBlock,
currentBlock,
createVNode
} from '../vnode'
import { isFunction, isArray, ShapeFlags, toNumber } from '@vue/shared'
import { ComponentInternalInstance, handleSetupResult } from '../component'
@ -79,7 +82,8 @@ export const SuspenseImpl = {
}
},
hydrate: hydrateSuspense,
create: createSuspenseBoundary
create: createSuspenseBoundary,
normalize: normalizeSuspenseChildren
}
// Force-casted public typing for h and TSX props inference
@ -709,31 +713,34 @@ function hydrateSuspense(
/* eslint-enable no-restricted-globals */
}
export function normalizeSuspenseChildren(
vnode: VNode
): {
content: VNode
fallback: VNode
} {
function normalizeSuspenseChildren(vnode: VNode) {
const { shapeFlag, children } = vnode
let content: VNode
let fallback: VNode
if (shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
content = normalizeSuspenseSlot((children as Slots).default)
fallback = normalizeSuspenseSlot((children as Slots).fallback)
} else {
content = normalizeSuspenseSlot(children as VNodeChild)
fallback = normalizeVNode(null)
}
return {
content,
fallback
}
const isSlotChildren = shapeFlag & ShapeFlags.SLOTS_CHILDREN
vnode.ssContent = normalizeSuspenseSlot(
isSlotChildren ? (children as Slots).default : children
)
vnode.ssFallback = isSlotChildren
? normalizeSuspenseSlot((children as Slots).fallback)
: createVNode(Comment)
}
function normalizeSuspenseSlot(s: any) {
let block: VNode[] | null | undefined
if (isFunction(s)) {
const isCompiledSlot = s._c
if (isCompiledSlot) {
// disableTracking: false
// allow block tracking for compiled slots
// (see ./componentRenderContext.ts)
s._d = false
openBlock()
}
s = s()
if (isCompiledSlot) {
s._d = true
block = currentBlock
closeBlock()
}
}
if (isArray(s)) {
const singleChild = filterSingleRoot(s)
@ -742,7 +749,11 @@ function normalizeSuspenseSlot(s: any) {
}
s = singleChild
}
return normalizeVNode(s)
s = normalizeVNode(s)
if (block) {
s.dynamicChildren = block.filter(c => c !== s)
}
return s
}
export function queueEffectWithSuspense(

View File

@ -26,8 +26,7 @@ import { AppContext } from './apiCreateApp'
import {
SuspenseImpl,
isSuspense,
SuspenseBoundary,
normalizeSuspenseChildren
SuspenseBoundary
} from './components/Suspense'
import { DirectiveBinding } from './directives'
import { TransitionHooks } from './components/BaseTransition'
@ -186,7 +185,7 @@ export interface VNode<
// structure would be stable. This allows us to skip most children diffing
// and only worry about the dynamic nodes (indicated by patch flags).
export const blockStack: (VNode[] | null)[] = []
let currentBlock: VNode[] | null = null
export let currentBlock: VNode[] | null = null
/**
* Open a block.
@ -452,9 +451,7 @@ function _createVNode(
// normalize suspense children
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
const { content, fallback } = normalizeSuspenseChildren(vnode)
vnode.ssContent = content
vnode.ssFallback = fallback
;(type as typeof SuspenseImpl).normalize(vnode)
}
if (