2020-02-16 02:48:45 +00:00
|
|
|
import { VNode, normalizeVNode, VNodeChild, VNodeProps } from '../vnode'
|
2020-02-14 06:36:42 +00:00
|
|
|
import { isFunction, isArray, ShapeFlags } from '@vue/shared'
|
2019-11-04 23:38:55 +00:00
|
|
|
import { ComponentInternalInstance, handleSetupResult } from '../component'
|
|
|
|
import { Slots } from '../componentSlots'
|
2020-03-23 15:08:22 +00:00
|
|
|
import {
|
|
|
|
RendererInternals,
|
|
|
|
MoveType,
|
|
|
|
SetupRenderEffectFn,
|
|
|
|
RendererNode,
|
|
|
|
RendererElement
|
|
|
|
} from '../renderer'
|
2019-11-04 23:38:55 +00:00
|
|
|
import { queuePostFlushCb, queueJob } from '../scheduler'
|
|
|
|
import { updateHOCHostEl } from '../componentRenderUtils'
|
|
|
|
import { pushWarningContext, popWarningContext } from '../warning'
|
2020-03-13 02:19:41 +00:00
|
|
|
import { handleError, ErrorCodes } from '../errorHandling'
|
2019-09-07 15:28:40 +00:00
|
|
|
|
2019-11-04 23:38:55 +00:00
|
|
|
export interface SuspenseProps {
|
|
|
|
onResolve?: () => void
|
|
|
|
onRecede?: () => void
|
|
|
|
}
|
|
|
|
|
2020-02-15 16:40:09 +00:00
|
|
|
export const isSuspense = (type: any): boolean => type.__isSuspense
|
|
|
|
|
2019-11-04 23:38:55 +00:00
|
|
|
// Suspense exposes a component-like API, and is treated like a component
|
|
|
|
// in the compiler, but internally it's a special built-in type that hooks
|
|
|
|
// directly into the renderer.
|
|
|
|
export const SuspenseImpl = {
|
|
|
|
// In order to make Suspense tree-shakable, we need to avoid importing it
|
|
|
|
// directly in the renderer. The renderer checks for the __isSuspense flag
|
|
|
|
// on a vnode's type and calls the `process` method, passing in renderer
|
|
|
|
// internals.
|
2019-11-01 16:43:27 +00:00
|
|
|
__isSuspense: true,
|
2019-10-29 16:30:09 +00:00
|
|
|
process(
|
|
|
|
n1: VNode | null,
|
|
|
|
n2: VNode,
|
2020-03-23 15:08:22 +00:00
|
|
|
container: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
2019-10-29 16:30:09 +00:00
|
|
|
parentComponent: ComponentInternalInstance | null,
|
|
|
|
parentSuspense: SuspenseBoundary | null,
|
|
|
|
isSVG: boolean,
|
|
|
|
optimized: boolean,
|
|
|
|
// platform-specific impl passed from renderer
|
|
|
|
rendererInternals: RendererInternals
|
|
|
|
) {
|
|
|
|
if (n1 == null) {
|
|
|
|
mountSuspense(
|
|
|
|
n2,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
parentSuspense,
|
|
|
|
isSVG,
|
|
|
|
optimized,
|
|
|
|
rendererInternals
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
patchSuspense(
|
|
|
|
n1,
|
|
|
|
n2,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
isSVG,
|
|
|
|
optimized,
|
|
|
|
rendererInternals
|
|
|
|
)
|
|
|
|
}
|
2020-03-13 02:19:41 +00:00
|
|
|
},
|
|
|
|
hydrate: hydrateSuspense
|
2019-10-29 16:30:09 +00:00
|
|
|
}
|
|
|
|
|
2019-11-04 23:38:55 +00:00
|
|
|
// Force-casted public typing for h and TSX props inference
|
|
|
|
export const Suspense = ((__FEATURE_SUSPENSE__
|
|
|
|
? SuspenseImpl
|
|
|
|
: null) as any) as {
|
|
|
|
__isSuspense: true
|
2020-02-16 02:48:45 +00:00
|
|
|
new (): { $props: VNodeProps & SuspenseProps }
|
2019-11-04 23:38:55 +00:00
|
|
|
}
|
|
|
|
|
2019-10-29 16:30:09 +00:00
|
|
|
function mountSuspense(
|
|
|
|
n2: VNode,
|
2020-03-23 15:08:22 +00:00
|
|
|
container: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
2019-10-29 16:30:09 +00:00
|
|
|
parentComponent: ComponentInternalInstance | null,
|
|
|
|
parentSuspense: SuspenseBoundary | null,
|
|
|
|
isSVG: boolean,
|
|
|
|
optimized: boolean,
|
|
|
|
rendererInternals: RendererInternals
|
|
|
|
) {
|
|
|
|
const {
|
2020-02-15 16:40:09 +00:00
|
|
|
p: patch,
|
|
|
|
o: { createElement }
|
2019-10-29 16:30:09 +00:00
|
|
|
} = rendererInternals
|
|
|
|
const hiddenContainer = createElement('div')
|
|
|
|
const suspense = (n2.suspense = createSuspenseBoundary(
|
|
|
|
n2,
|
|
|
|
parentSuspense,
|
|
|
|
parentComponent,
|
|
|
|
container,
|
|
|
|
hiddenContainer,
|
|
|
|
anchor,
|
|
|
|
isSVG,
|
|
|
|
optimized,
|
|
|
|
rendererInternals
|
|
|
|
))
|
|
|
|
|
|
|
|
// start mounting the content subtree in an off-dom container
|
|
|
|
patch(
|
|
|
|
null,
|
2020-03-13 02:19:41 +00:00
|
|
|
suspense.subTree,
|
2019-10-29 16:30:09 +00:00
|
|
|
hiddenContainer,
|
|
|
|
null,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
|
|
|
isSVG,
|
|
|
|
optimized
|
|
|
|
)
|
|
|
|
// now check if we have encountered any async deps
|
|
|
|
if (suspense.deps > 0) {
|
|
|
|
// mount the fallback tree
|
|
|
|
patch(
|
|
|
|
null,
|
2020-03-13 02:19:41 +00:00
|
|
|
suspense.fallbackTree,
|
2019-10-29 16:30:09 +00:00
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
null, // fallback tree will not have suspense context
|
|
|
|
isSVG,
|
|
|
|
optimized
|
|
|
|
)
|
2020-03-13 02:19:41 +00:00
|
|
|
n2.el = suspense.fallbackTree.el
|
2019-10-29 16:30:09 +00:00
|
|
|
} else {
|
|
|
|
// Suspense has no async deps. Just resolve.
|
|
|
|
suspense.resolve()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function patchSuspense(
|
|
|
|
n1: VNode,
|
|
|
|
n2: VNode,
|
2020-03-23 15:08:22 +00:00
|
|
|
container: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
2019-10-29 16:30:09 +00:00
|
|
|
parentComponent: ComponentInternalInstance | null,
|
|
|
|
isSVG: boolean,
|
|
|
|
optimized: boolean,
|
2020-02-15 16:40:09 +00:00
|
|
|
{ p: patch }: RendererInternals
|
2019-10-29 16:30:09 +00:00
|
|
|
) {
|
|
|
|
const suspense = (n2.suspense = n1.suspense)!
|
|
|
|
suspense.vnode = n2
|
|
|
|
const { content, fallback } = normalizeSuspenseChildren(n2)
|
|
|
|
const oldSubTree = suspense.subTree
|
|
|
|
const oldFallbackTree = suspense.fallbackTree
|
|
|
|
if (!suspense.isResolved) {
|
|
|
|
patch(
|
|
|
|
oldSubTree,
|
|
|
|
content,
|
|
|
|
suspense.hiddenContainer,
|
|
|
|
null,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
|
|
|
isSVG,
|
|
|
|
optimized
|
|
|
|
)
|
|
|
|
if (suspense.deps > 0) {
|
|
|
|
// still pending. patch the fallback tree.
|
|
|
|
patch(
|
|
|
|
oldFallbackTree,
|
|
|
|
fallback,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
null, // fallback tree will not have suspense context
|
|
|
|
isSVG,
|
|
|
|
optimized
|
|
|
|
)
|
|
|
|
n2.el = fallback.el
|
|
|
|
}
|
|
|
|
// If deps somehow becomes 0 after the patch it means the patch caused an
|
|
|
|
// async dep component to unmount and removed its dep. It will cause the
|
|
|
|
// suspense to resolve and we don't need to do anything here.
|
|
|
|
} else {
|
|
|
|
// just normal patch inner content as a fragment
|
|
|
|
patch(
|
|
|
|
oldSubTree,
|
|
|
|
content,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
|
|
|
isSVG,
|
|
|
|
optimized
|
|
|
|
)
|
|
|
|
n2.el = content.el
|
|
|
|
}
|
|
|
|
suspense.subTree = content
|
|
|
|
suspense.fallbackTree = fallback
|
|
|
|
}
|
2019-09-07 15:28:40 +00:00
|
|
|
|
2020-03-23 15:08:22 +00:00
|
|
|
export interface SuspenseBoundary {
|
|
|
|
vnode: VNode
|
|
|
|
parent: SuspenseBoundary | null
|
2019-09-11 21:38:26 +00:00
|
|
|
parentComponent: ComponentInternalInstance | null
|
2019-09-12 16:16:01 +00:00
|
|
|
isSVG: boolean
|
|
|
|
optimized: boolean
|
2020-03-23 15:08:22 +00:00
|
|
|
container: RendererElement
|
|
|
|
hiddenContainer: RendererElement
|
|
|
|
anchor: RendererNode | null
|
|
|
|
subTree: VNode
|
|
|
|
fallbackTree: VNode
|
2019-09-07 15:28:40 +00:00
|
|
|
deps: number
|
2020-03-13 02:19:41 +00:00
|
|
|
isHydrating: boolean
|
2019-09-07 15:28:40 +00:00
|
|
|
isResolved: boolean
|
2019-09-11 17:22:18 +00:00
|
|
|
isUnmounted: boolean
|
2019-09-11 00:53:28 +00:00
|
|
|
effects: Function[]
|
2019-10-29 16:30:09 +00:00
|
|
|
resolve(): void
|
2019-10-29 18:04:44 +00:00
|
|
|
recede(): void
|
2020-03-23 15:08:22 +00:00
|
|
|
move(
|
|
|
|
container: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
|
|
|
type: MoveType
|
|
|
|
): void
|
|
|
|
next(): RendererNode | null
|
2019-10-29 16:30:09 +00:00
|
|
|
registerDep(
|
|
|
|
instance: ComponentInternalInstance,
|
2020-03-23 15:08:22 +00:00
|
|
|
setupRenderEffect: SetupRenderEffectFn
|
2019-10-29 16:30:09 +00:00
|
|
|
): void
|
2020-03-23 15:08:22 +00:00
|
|
|
unmount(parentSuspense: SuspenseBoundary | null, doRemove?: boolean): void
|
2019-09-07 15:28:40 +00:00
|
|
|
}
|
|
|
|
|
2020-04-24 20:13:44 +00:00
|
|
|
let hasWarned = false
|
|
|
|
|
2020-03-23 15:08:22 +00:00
|
|
|
function createSuspenseBoundary(
|
|
|
|
vnode: VNode,
|
|
|
|
parent: SuspenseBoundary | null,
|
2019-09-11 21:38:26 +00:00
|
|
|
parentComponent: ComponentInternalInstance | null,
|
2020-03-23 15:08:22 +00:00
|
|
|
container: RendererElement,
|
|
|
|
hiddenContainer: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
2019-09-12 16:16:01 +00:00
|
|
|
isSVG: boolean,
|
2019-10-29 16:30:09 +00:00
|
|
|
optimized: boolean,
|
2020-03-23 15:08:22 +00:00
|
|
|
rendererInternals: RendererInternals,
|
2020-03-13 02:19:41 +00:00
|
|
|
isHydrating = false
|
2020-03-23 15:08:22 +00:00
|
|
|
): SuspenseBoundary {
|
2020-04-24 20:13:44 +00:00
|
|
|
/* istanbul ignore if */
|
|
|
|
if (__DEV__ && !__TEST__ && !hasWarned) {
|
|
|
|
hasWarned = true
|
|
|
|
console[console.info ? 'info' : 'log'](
|
|
|
|
`<Suspense> is an experimental feature and its API will likely change.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-10-29 16:30:09 +00:00
|
|
|
const {
|
2020-02-15 16:40:09 +00:00
|
|
|
p: patch,
|
|
|
|
m: move,
|
|
|
|
um: unmount,
|
|
|
|
n: next,
|
|
|
|
o: { parentNode }
|
2019-10-29 16:30:09 +00:00
|
|
|
} = rendererInternals
|
|
|
|
|
2020-03-13 02:19:41 +00:00
|
|
|
const getCurrentTree = () =>
|
|
|
|
suspense.isResolved || suspense.isHydrating
|
|
|
|
? suspense.subTree
|
|
|
|
: suspense.fallbackTree
|
|
|
|
|
|
|
|
const { content, fallback } = normalizeSuspenseChildren(vnode)
|
2020-03-23 15:08:22 +00:00
|
|
|
const suspense: SuspenseBoundary = {
|
2019-09-09 20:00:50 +00:00
|
|
|
vnode,
|
2019-09-09 17:59:53 +00:00
|
|
|
parent,
|
2019-09-11 21:38:26 +00:00
|
|
|
parentComponent,
|
2019-09-12 16:16:01 +00:00
|
|
|
isSVG,
|
|
|
|
optimized,
|
2019-09-10 15:01:11 +00:00
|
|
|
container,
|
2019-09-11 21:38:26 +00:00
|
|
|
hiddenContainer,
|
|
|
|
anchor,
|
2019-09-07 15:28:40 +00:00
|
|
|
deps: 0,
|
2020-03-13 02:19:41 +00:00
|
|
|
subTree: content,
|
|
|
|
fallbackTree: fallback,
|
|
|
|
isHydrating,
|
2019-09-07 15:28:40 +00:00
|
|
|
isResolved: false,
|
2019-09-11 17:22:18 +00:00
|
|
|
isUnmounted: false,
|
2019-10-29 16:30:09 +00:00
|
|
|
effects: [],
|
|
|
|
|
|
|
|
resolve() {
|
|
|
|
if (__DEV__) {
|
|
|
|
if (suspense.isResolved) {
|
|
|
|
throw new Error(
|
|
|
|
`resolveSuspense() is called on an already resolved suspense boundary.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (suspense.isUnmounted) {
|
|
|
|
throw new Error(
|
|
|
|
`resolveSuspense() is called on an already unmounted suspense boundary.`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const {
|
|
|
|
vnode,
|
|
|
|
subTree,
|
|
|
|
fallbackTree,
|
|
|
|
effects,
|
|
|
|
parentComponent,
|
|
|
|
container
|
|
|
|
} = suspense
|
|
|
|
|
2020-03-13 02:19:41 +00:00
|
|
|
if (suspense.isHydrating) {
|
|
|
|
suspense.isHydrating = false
|
|
|
|
} else {
|
|
|
|
// this is initial anchor on mount
|
|
|
|
let { anchor } = suspense
|
|
|
|
// unmount fallback tree
|
|
|
|
if (fallbackTree.el) {
|
|
|
|
// if the fallback tree was mounted, it may have been moved
|
|
|
|
// as part of a parent suspense. get the latest anchor for insertion
|
|
|
|
anchor = next(fallbackTree)
|
|
|
|
unmount(fallbackTree, parentComponent, suspense, true)
|
|
|
|
}
|
|
|
|
// move content from off-dom container to actual container
|
|
|
|
move(subTree, container, anchor, MoveType.ENTER)
|
2019-10-29 16:30:09 +00:00
|
|
|
}
|
2020-03-13 02:19:41 +00:00
|
|
|
|
2020-03-11 15:17:10 +00:00
|
|
|
const el = (vnode.el = subTree.el!)
|
2019-10-29 16:30:09 +00:00
|
|
|
// suspense as the root node of a component...
|
|
|
|
if (parentComponent && parentComponent.subTree === vnode) {
|
|
|
|
parentComponent.vnode.el = el
|
|
|
|
updateHOCHostEl(parentComponent, el)
|
|
|
|
}
|
|
|
|
// check if there is a pending parent suspense
|
|
|
|
let parent = suspense.parent
|
|
|
|
let hasUnresolvedAncestor = false
|
|
|
|
while (parent) {
|
|
|
|
if (!parent.isResolved) {
|
|
|
|
// found a pending parent suspense, merge buffered post jobs
|
|
|
|
// into that parent
|
|
|
|
parent.effects.push(...effects)
|
|
|
|
hasUnresolvedAncestor = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
parent = parent.parent
|
|
|
|
}
|
|
|
|
// no pending parent suspense, flush all jobs
|
|
|
|
if (!hasUnresolvedAncestor) {
|
|
|
|
queuePostFlushCb(effects)
|
|
|
|
}
|
|
|
|
suspense.isResolved = true
|
2020-03-18 19:40:20 +00:00
|
|
|
suspense.effects = []
|
2019-10-29 16:30:09 +00:00
|
|
|
// invoke @resolve event
|
|
|
|
const onResolve = vnode.props && vnode.props.onResolve
|
|
|
|
if (isFunction(onResolve)) {
|
|
|
|
onResolve()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-10-29 18:04:44 +00:00
|
|
|
recede() {
|
2019-10-29 16:30:09 +00:00
|
|
|
suspense.isResolved = false
|
|
|
|
const {
|
|
|
|
vnode,
|
|
|
|
subTree,
|
|
|
|
fallbackTree,
|
|
|
|
parentComponent,
|
|
|
|
container,
|
|
|
|
hiddenContainer,
|
|
|
|
isSVG,
|
|
|
|
optimized
|
|
|
|
} = suspense
|
|
|
|
|
|
|
|
// move content tree back to the off-dom container
|
|
|
|
const anchor = next(subTree)
|
2020-03-11 15:17:10 +00:00
|
|
|
move(subTree, hiddenContainer, null, MoveType.LEAVE)
|
2019-10-29 16:30:09 +00:00
|
|
|
// remount the fallback tree
|
|
|
|
patch(
|
|
|
|
null,
|
|
|
|
fallbackTree,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
null, // fallback tree will not have suspense context
|
|
|
|
isSVG,
|
|
|
|
optimized
|
|
|
|
)
|
2020-03-11 15:17:10 +00:00
|
|
|
const el = (vnode.el = fallbackTree.el!)
|
2019-10-29 16:30:09 +00:00
|
|
|
// suspense as the root node of a component...
|
|
|
|
if (parentComponent && parentComponent.subTree === vnode) {
|
|
|
|
parentComponent.vnode.el = el
|
|
|
|
updateHOCHostEl(parentComponent, el)
|
|
|
|
}
|
|
|
|
|
2019-10-29 18:04:44 +00:00
|
|
|
// invoke @recede event
|
|
|
|
const onRecede = vnode.props && vnode.props.onRecede
|
|
|
|
if (isFunction(onRecede)) {
|
|
|
|
onRecede()
|
2019-10-29 16:30:09 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-11-25 22:34:28 +00:00
|
|
|
move(container, anchor, type) {
|
2020-03-13 02:19:41 +00:00
|
|
|
move(getCurrentTree(), container, anchor, type)
|
2019-10-29 16:40:54 +00:00
|
|
|
suspense.container = container
|
|
|
|
},
|
|
|
|
|
|
|
|
next() {
|
2020-03-13 02:19:41 +00:00
|
|
|
return next(getCurrentTree())
|
2019-10-29 16:40:54 +00:00
|
|
|
},
|
|
|
|
|
2019-10-29 16:30:09 +00:00
|
|
|
registerDep(instance, setupRenderEffect) {
|
|
|
|
// suspense is already resolved, need to recede.
|
|
|
|
// use queueJob so it's handled synchronously after patching the current
|
|
|
|
// suspense tree
|
|
|
|
if (suspense.isResolved) {
|
|
|
|
queueJob(() => {
|
2019-10-29 18:04:44 +00:00
|
|
|
suspense.recede()
|
2019-10-29 16:30:09 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-03-13 02:19:41 +00:00
|
|
|
const hydratedEl = instance.vnode.el
|
2019-10-29 16:30:09 +00:00
|
|
|
suspense.deps++
|
|
|
|
instance
|
|
|
|
.asyncDep!.catch(err => {
|
|
|
|
handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
|
|
|
|
})
|
|
|
|
.then(asyncSetupResult => {
|
|
|
|
// retry when the setup() promise resolves.
|
|
|
|
// component may have been unmounted before resolve.
|
|
|
|
if (instance.isUnmounted || suspense.isUnmounted) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
suspense.deps--
|
|
|
|
// retry from this component
|
|
|
|
instance.asyncResolved = true
|
|
|
|
const { vnode } = instance
|
|
|
|
if (__DEV__) {
|
|
|
|
pushWarningContext(vnode)
|
|
|
|
}
|
2020-04-05 22:39:22 +00:00
|
|
|
handleSetupResult(instance, asyncSetupResult, false)
|
2020-03-13 02:19:41 +00:00
|
|
|
if (hydratedEl) {
|
|
|
|
// vnode may have been replaced if an update happened before the
|
2020-05-01 13:42:58 +00:00
|
|
|
// async dep is resolved.
|
2020-03-13 02:19:41 +00:00
|
|
|
vnode.el = hydratedEl
|
|
|
|
}
|
2019-10-29 16:30:09 +00:00
|
|
|
setupRenderEffect(
|
|
|
|
instance,
|
|
|
|
vnode,
|
2020-03-13 02:19:41 +00:00
|
|
|
// component may have been moved before resolve.
|
|
|
|
// if this is not a hydration, instance.subTree will be the comment
|
|
|
|
// placeholder.
|
|
|
|
hydratedEl
|
|
|
|
? parentNode(hydratedEl)!
|
2020-03-23 15:08:22 +00:00
|
|
|
: parentNode(instance.subTree.el!)!,
|
2020-03-13 02:19:41 +00:00
|
|
|
// anchor will not be used if this is hydration, so only need to
|
|
|
|
// consider the comment placeholder case.
|
|
|
|
hydratedEl ? null : next(instance.subTree),
|
2020-02-14 04:31:03 +00:00
|
|
|
suspense,
|
2020-04-06 21:37:47 +00:00
|
|
|
isSVG,
|
|
|
|
optimized
|
2019-10-29 16:30:09 +00:00
|
|
|
)
|
|
|
|
updateHOCHostEl(instance, vnode.el)
|
|
|
|
if (__DEV__) {
|
|
|
|
popWarningContext()
|
|
|
|
}
|
|
|
|
if (suspense.deps === 0) {
|
|
|
|
suspense.resolve()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
unmount(parentSuspense, doRemove) {
|
|
|
|
suspense.isUnmounted = true
|
|
|
|
unmount(suspense.subTree, parentComponent, parentSuspense, doRemove)
|
|
|
|
if (!suspense.isResolved) {
|
|
|
|
unmount(
|
|
|
|
suspense.fallbackTree,
|
|
|
|
parentComponent,
|
|
|
|
parentSuspense,
|
|
|
|
doRemove
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2019-09-07 15:28:40 +00:00
|
|
|
}
|
2019-10-29 16:30:09 +00:00
|
|
|
|
|
|
|
return suspense
|
2019-09-07 15:28:40 +00:00
|
|
|
}
|
2019-09-10 15:01:11 +00:00
|
|
|
|
2020-03-13 02:19:41 +00:00
|
|
|
function hydrateSuspense(
|
|
|
|
node: Node,
|
|
|
|
vnode: VNode,
|
|
|
|
parentComponent: ComponentInternalInstance | null,
|
|
|
|
parentSuspense: SuspenseBoundary | null,
|
|
|
|
isSVG: boolean,
|
|
|
|
optimized: boolean,
|
|
|
|
rendererInternals: RendererInternals,
|
|
|
|
hydrateNode: (
|
|
|
|
node: Node,
|
|
|
|
vnode: VNode,
|
|
|
|
parentComponent: ComponentInternalInstance | null,
|
|
|
|
parentSuspense: SuspenseBoundary | null,
|
|
|
|
optimized: boolean
|
|
|
|
) => Node | null
|
|
|
|
): Node | null {
|
|
|
|
const suspense = (vnode.suspense = createSuspenseBoundary(
|
|
|
|
vnode,
|
|
|
|
parentSuspense,
|
|
|
|
parentComponent,
|
2020-03-23 15:08:22 +00:00
|
|
|
node.parentNode!,
|
2020-03-13 02:19:41 +00:00
|
|
|
document.createElement('div'),
|
|
|
|
null,
|
|
|
|
isSVG,
|
|
|
|
optimized,
|
|
|
|
rendererInternals,
|
|
|
|
true /* hydrating */
|
|
|
|
))
|
|
|
|
// there are two possible scenarios for server-rendered suspense:
|
|
|
|
// - success: ssr content should be fully resolved
|
|
|
|
// - failure: ssr content should be the fallback branch.
|
|
|
|
// however, on the client we don't really know if it has failed or not
|
|
|
|
// attempt to hydrate the DOM assuming it has succeeded, but we still
|
|
|
|
// need to construct a suspense boundary first
|
|
|
|
const result = hydrateNode(
|
|
|
|
node,
|
|
|
|
suspense.subTree,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
|
|
|
optimized
|
|
|
|
)
|
|
|
|
if (suspense.deps === 0) {
|
|
|
|
suspense.resolve()
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-03-09 22:20:30 +00:00
|
|
|
export function normalizeSuspenseChildren(
|
2019-09-10 15:01:11 +00:00
|
|
|
vnode: VNode
|
|
|
|
): {
|
|
|
|
content: VNode
|
|
|
|
fallback: VNode
|
|
|
|
} {
|
2019-09-10 15:17:26 +00:00
|
|
|
const { shapeFlag, children } = vnode
|
2019-09-10 15:01:11 +00:00
|
|
|
if (shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
2019-10-08 16:43:13 +00:00
|
|
|
const { default: d, fallback } = children as Slots
|
2019-09-10 15:01:11 +00:00
|
|
|
return {
|
2019-09-10 15:17:26 +00:00
|
|
|
content: normalizeVNode(isFunction(d) ? d() : d),
|
|
|
|
fallback: normalizeVNode(isFunction(fallback) ? fallback() : fallback)
|
2019-09-10 15:01:11 +00:00
|
|
|
}
|
2019-10-13 02:52:11 +00:00
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
content: normalizeVNode(children as VNodeChild),
|
|
|
|
fallback: normalizeVNode(null)
|
|
|
|
}
|
2019-09-10 15:01:11 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-29 16:40:54 +00:00
|
|
|
|
|
|
|
export function queueEffectWithSuspense(
|
|
|
|
fn: Function | Function[],
|
|
|
|
suspense: SuspenseBoundary | null
|
|
|
|
): void {
|
2020-03-18 22:14:51 +00:00
|
|
|
if (suspense && !suspense.isResolved) {
|
2019-10-29 16:40:54 +00:00
|
|
|
if (isArray(fn)) {
|
|
|
|
suspense.effects.push(...fn)
|
|
|
|
} else {
|
|
|
|
suspense.effects.push(fn)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
queuePostFlushCb(fn)
|
|
|
|
}
|
|
|
|
}
|