2020-09-16 00:45:06 +08:00
|
|
|
import {
|
2021-09-02 04:57:56 +08:00
|
|
|
Comment,
|
2019-11-05 07:38:55 +08:00
|
|
|
type VNode,
|
|
|
|
type VNodeProps,
|
2021-05-28 04:32:31 +08:00
|
|
|
closeBlock,
|
2021-09-17 00:14:33 +08:00
|
|
|
createVNode,
|
2021-05-28 04:32:31 +08:00
|
|
|
currentBlock,
|
2021-09-17 00:14:33 +08:00
|
|
|
isBlockTreeEnabled,
|
2021-05-28 04:32:31 +08:00
|
|
|
isSameVNodeType,
|
2020-09-16 00:45:06 +08:00
|
|
|
normalizeVNode,
|
2021-05-28 04:32:31 +08:00
|
|
|
openBlock,
|
2020-09-16 00:45:06 +08:00
|
|
|
} from '../vnode'
|
|
|
|
import { ShapeFlags, isArray, isFunction, toNumber } from '@vue/shared'
|
2019-11-05 07:38:55 +08:00
|
|
|
import { type ComponentInternalInstance, handleSetupResult } from '../component'
|
|
|
|
import type { Slots } from '../componentSlots'
|
2020-03-23 23:08:22 +08:00
|
|
|
import {
|
|
|
|
type ElementNamespace,
|
|
|
|
MoveType,
|
2020-07-17 23:43:28 +08:00
|
|
|
type RendererElement,
|
2020-03-23 23:08:22 +08:00
|
|
|
type RendererInternals,
|
2019-11-05 07:38:55 +08:00
|
|
|
type RendererNode,
|
2020-03-23 23:08:22 +08:00
|
|
|
type SetupRenderEffectFn,
|
|
|
|
} from '../renderer'
|
2020-09-16 00:45:06 +08:00
|
|
|
import { queuePostFlushCb } from '../scheduler'
|
|
|
|
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
|
2022-11-14 16:20:12 +08:00
|
|
|
import {
|
|
|
|
assertNumber,
|
|
|
|
popWarningContext,
|
|
|
|
pushWarningContext,
|
|
|
|
warn,
|
|
|
|
} from '../warning'
|
2020-03-13 10:19:41 +08:00
|
|
|
import { ErrorCodes, handleError } from '../errorHandling'
|
2023-11-06 17:48:40 +08:00
|
|
|
import { NULL_DYNAMIC_COMPONENT } from '../helpers/resolveAssets'
|
2019-09-07 23:28:40 +08:00
|
|
|
|
2019-11-05 07:38:55 +08:00
|
|
|
export interface SuspenseProps {
|
|
|
|
onResolve?: () => void
|
2020-09-16 00:45:06 +08:00
|
|
|
onPending?: () => void
|
|
|
|
onFallback?: () => void
|
|
|
|
timeout?: string | number
|
2023-04-21 14:43:30 +08:00
|
|
|
/**
|
|
|
|
* Allow suspense to be captured by parent suspense
|
|
|
|
*
|
|
|
|
* @default false
|
|
|
|
*/
|
|
|
|
suspensible?: boolean
|
2019-11-05 07:38:55 +08:00
|
|
|
}
|
|
|
|
|
2020-02-16 00:40:09 +08:00
|
|
|
export const isSuspense = (type: any): boolean => type.__isSuspense
|
|
|
|
|
2023-12-12 23:50:28 +08:00
|
|
|
// incrementing unique id for every pending branch
|
|
|
|
let suspenseId = 0
|
|
|
|
|
2024-01-03 17:22:06 +08:00
|
|
|
/**
|
|
|
|
* For testing only
|
|
|
|
*/
|
|
|
|
export const resetSuspenseId = () => (suspenseId = 0)
|
|
|
|
|
2019-11-05 07:38:55 +08: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 = {
|
2021-03-23 17:17:15 +08:00
|
|
|
name: 'Suspense',
|
2019-11-05 07:38:55 +08:00
|
|
|
// 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-02 00:43:27 +08:00
|
|
|
__isSuspense: true,
|
2019-10-30 00:30:09 +08:00
|
|
|
process(
|
|
|
|
n1: VNode | null,
|
|
|
|
n2: VNode,
|
2020-03-23 23:08:22 +08:00
|
|
|
container: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
2019-10-30 00:30:09 +08:00
|
|
|
parentComponent: ComponentInternalInstance | null,
|
|
|
|
parentSuspense: SuspenseBoundary | null,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace: ElementNamespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds: string[] | null,
|
2019-10-30 00:30:09 +08:00
|
|
|
optimized: boolean,
|
|
|
|
// platform-specific impl passed from renderer
|
|
|
|
rendererInternals: RendererInternals,
|
|
|
|
) {
|
|
|
|
if (n1 == null) {
|
|
|
|
mountSuspense(
|
|
|
|
n2,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
parentSuspense,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
2019-10-30 00:30:09 +08:00
|
|
|
optimized,
|
|
|
|
rendererInternals,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
patchSuspense(
|
|
|
|
n1,
|
|
|
|
n2,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
2019-10-30 00:30:09 +08:00
|
|
|
rendererInternals,
|
|
|
|
)
|
|
|
|
}
|
2020-03-13 10:19:41 +08:00
|
|
|
},
|
2020-09-16 00:45:06 +08:00
|
|
|
hydrate: hydrateSuspense,
|
2021-05-28 04:32:31 +08:00
|
|
|
create: createSuspenseBoundary,
|
|
|
|
normalize: normalizeSuspenseChildren,
|
2019-10-30 00:30:09 +08:00
|
|
|
}
|
|
|
|
|
2019-11-05 07:38:55 +08:00
|
|
|
// Force-casted public typing for h and TSX props inference
|
2022-10-03 16:37:54 +08:00
|
|
|
export const Suspense = (__FEATURE_SUSPENSE__
|
|
|
|
? SuspenseImpl
|
|
|
|
: null) as unknown as {
|
2019-11-05 07:38:55 +08:00
|
|
|
__isSuspense: true
|
2023-05-05 17:12:51 +08:00
|
|
|
new (): {
|
|
|
|
$props: VNodeProps & SuspenseProps
|
|
|
|
$slots: {
|
|
|
|
default(): VNode[]
|
|
|
|
fallback(): VNode[]
|
|
|
|
}
|
|
|
|
}
|
2019-11-05 07:38:55 +08:00
|
|
|
}
|
|
|
|
|
2021-06-22 05:03:07 +08:00
|
|
|
function triggerEvent(
|
|
|
|
vnode: VNode,
|
|
|
|
name: 'onResolve' | 'onPending' | 'onFallback',
|
|
|
|
) {
|
|
|
|
const eventListener = vnode.props && vnode.props[name]
|
|
|
|
if (isFunction(eventListener)) {
|
|
|
|
eventListener()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-30 00:30:09 +08:00
|
|
|
function mountSuspense(
|
2020-09-16 00:45:06 +08:00
|
|
|
vnode: VNode,
|
2020-03-23 23:08:22 +08:00
|
|
|
container: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
2019-10-30 00:30:09 +08:00
|
|
|
parentComponent: ComponentInternalInstance | null,
|
|
|
|
parentSuspense: SuspenseBoundary | null,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace: ElementNamespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds: string[] | null,
|
2019-10-30 00:30:09 +08:00
|
|
|
optimized: boolean,
|
|
|
|
rendererInternals: RendererInternals,
|
|
|
|
) {
|
|
|
|
const {
|
2020-02-16 00:40:09 +08:00
|
|
|
p: patch,
|
|
|
|
o: { createElement },
|
2019-10-30 00:30:09 +08:00
|
|
|
} = rendererInternals
|
|
|
|
const hiddenContainer = createElement('div')
|
2020-09-16 00:45:06 +08:00
|
|
|
const suspense = (vnode.suspense = createSuspenseBoundary(
|
|
|
|
vnode,
|
2019-10-30 00:30:09 +08:00
|
|
|
parentSuspense,
|
|
|
|
parentComponent,
|
|
|
|
container,
|
|
|
|
hiddenContainer,
|
|
|
|
anchor,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
2019-10-30 00:30:09 +08:00
|
|
|
optimized,
|
|
|
|
rendererInternals,
|
|
|
|
))
|
|
|
|
|
|
|
|
// start mounting the content subtree in an off-dom container
|
|
|
|
patch(
|
|
|
|
null,
|
2020-09-16 00:45:06 +08:00
|
|
|
(suspense.pendingBranch = vnode.ssContent!),
|
2019-10-30 00:30:09 +08:00
|
|
|
hiddenContainer,
|
|
|
|
null,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
2019-10-30 00:30:09 +08:00
|
|
|
)
|
|
|
|
// now check if we have encountered any async deps
|
|
|
|
if (suspense.deps > 0) {
|
2020-09-16 00:45:06 +08:00
|
|
|
// has async
|
2021-06-22 05:03:07 +08:00
|
|
|
// invoke @fallback event
|
|
|
|
triggerEvent(vnode, 'onPending')
|
|
|
|
triggerEvent(vnode, 'onFallback')
|
|
|
|
|
2019-10-30 00:30:09 +08:00
|
|
|
// mount the fallback tree
|
|
|
|
patch(
|
|
|
|
null,
|
2020-09-16 00:45:06 +08:00
|
|
|
vnode.ssFallback!,
|
2019-10-30 00:30:09 +08:00
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
null, // fallback tree will not have suspense context
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
2019-10-30 00:30:09 +08:00
|
|
|
)
|
2020-09-16 00:45:06 +08:00
|
|
|
setActiveBranch(suspense, vnode.ssFallback!)
|
2019-10-30 00:30:09 +08:00
|
|
|
} else {
|
|
|
|
// Suspense has no async deps. Just resolve.
|
2023-05-08 16:37:46 +08:00
|
|
|
suspense.resolve(false, true)
|
2019-10-30 00:30:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function patchSuspense(
|
|
|
|
n1: VNode,
|
|
|
|
n2: VNode,
|
2020-03-23 23:08:22 +08:00
|
|
|
container: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
2019-10-30 00:30:09 +08:00
|
|
|
parentComponent: ComponentInternalInstance | null,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace: ElementNamespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds: string[] | null,
|
|
|
|
optimized: boolean,
|
2020-09-16 00:45:06 +08:00
|
|
|
{ p: patch, um: unmount, o: { createElement } }: RendererInternals,
|
2019-10-30 00:30:09 +08:00
|
|
|
) {
|
|
|
|
const suspense = (n2.suspense = n1.suspense)!
|
|
|
|
suspense.vnode = n2
|
2020-09-16 00:45:06 +08:00
|
|
|
n2.el = n1.el
|
|
|
|
const newBranch = n2.ssContent!
|
|
|
|
const newFallback = n2.ssFallback!
|
|
|
|
|
|
|
|
const { activeBranch, pendingBranch, isInFallback, isHydrating } = suspense
|
|
|
|
if (pendingBranch) {
|
|
|
|
suspense.pendingBranch = newBranch
|
|
|
|
if (isSameVNodeType(newBranch, pendingBranch)) {
|
|
|
|
// same root type but content may have changed.
|
2019-10-30 00:30:09 +08:00
|
|
|
patch(
|
2020-09-16 00:45:06 +08:00
|
|
|
pendingBranch,
|
|
|
|
newBranch,
|
|
|
|
suspense.hiddenContainer,
|
|
|
|
null,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
2020-09-16 00:45:06 +08:00
|
|
|
)
|
|
|
|
if (suspense.deps <= 0) {
|
|
|
|
suspense.resolve()
|
|
|
|
} else if (isInFallback) {
|
2023-12-12 21:55:15 +08:00
|
|
|
// It's possible that the app is in hydrating state when patching the
|
|
|
|
// suspense instance. If someone updates the dependency during component
|
|
|
|
// setup in children of suspense boundary, that would be problemtic
|
|
|
|
// because we aren't actually showing a fallback content when
|
|
|
|
// patchSuspense is called. In such case, patch of fallback content
|
|
|
|
// should be no op
|
|
|
|
if (!isHydrating) {
|
|
|
|
patch(
|
|
|
|
activeBranch,
|
|
|
|
newFallback,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
null, // fallback tree will not have suspense context
|
|
|
|
namespace,
|
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
|
|
|
)
|
|
|
|
setActiveBranch(suspense, newFallback)
|
|
|
|
}
|
2020-09-16 00:45:06 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// toggled before pending tree is resolved
|
2023-12-12 23:50:28 +08:00
|
|
|
// increment pending ID. this is used to invalidate async callbacks
|
|
|
|
suspense.pendingId = suspenseId++
|
2020-09-16 00:45:06 +08:00
|
|
|
if (isHydrating) {
|
|
|
|
// if toggled before hydration is finished, the current DOM tree is
|
|
|
|
// no longer valid. set it as the active branch so it will be unmounted
|
|
|
|
// when resolved
|
|
|
|
suspense.isHydrating = false
|
|
|
|
suspense.activeBranch = pendingBranch
|
|
|
|
} else {
|
2020-09-18 11:49:06 +08:00
|
|
|
unmount(pendingBranch, parentComponent, suspense)
|
2020-09-16 00:45:06 +08:00
|
|
|
}
|
|
|
|
// reset suspense state
|
|
|
|
suspense.deps = 0
|
2020-09-18 11:49:06 +08:00
|
|
|
// discard effects from pending branch
|
2020-09-16 00:45:06 +08:00
|
|
|
suspense.effects.length = 0
|
|
|
|
// discard previous container
|
|
|
|
suspense.hiddenContainer = createElement('div')
|
|
|
|
|
|
|
|
if (isInFallback) {
|
|
|
|
// already in fallback state
|
|
|
|
patch(
|
|
|
|
null,
|
|
|
|
newBranch,
|
|
|
|
suspense.hiddenContainer,
|
|
|
|
null,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
2020-09-16 00:45:06 +08:00
|
|
|
)
|
|
|
|
if (suspense.deps <= 0) {
|
|
|
|
suspense.resolve()
|
|
|
|
} else {
|
|
|
|
patch(
|
|
|
|
activeBranch,
|
|
|
|
newFallback,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
null, // fallback tree will not have suspense context
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
2020-09-16 00:45:06 +08:00
|
|
|
)
|
|
|
|
setActiveBranch(suspense, newFallback)
|
|
|
|
}
|
|
|
|
} else if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
|
|
|
|
// toggled "back" to current active branch
|
|
|
|
patch(
|
|
|
|
activeBranch,
|
|
|
|
newBranch,
|
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
2020-09-16 00:45:06 +08:00
|
|
|
)
|
|
|
|
// force resolve
|
|
|
|
suspense.resolve(true)
|
|
|
|
} else {
|
|
|
|
// switched to a 3rd branch
|
|
|
|
patch(
|
|
|
|
null,
|
|
|
|
newBranch,
|
|
|
|
suspense.hiddenContainer,
|
|
|
|
null,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
2020-09-16 00:45:06 +08:00
|
|
|
)
|
|
|
|
if (suspense.deps <= 0) {
|
|
|
|
suspense.resolve()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
|
|
|
|
// root did not change, just normal patch
|
|
|
|
patch(
|
|
|
|
activeBranch,
|
|
|
|
newBranch,
|
2019-10-30 00:30:09 +08:00
|
|
|
container,
|
|
|
|
anchor,
|
|
|
|
parentComponent,
|
2020-09-16 00:45:06 +08:00
|
|
|
suspense,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
2019-10-30 00:30:09 +08:00
|
|
|
)
|
2020-09-16 00:45:06 +08:00
|
|
|
setActiveBranch(suspense, newBranch)
|
|
|
|
} else {
|
|
|
|
// root node toggled
|
|
|
|
// invoke @pending event
|
2021-06-22 05:03:07 +08:00
|
|
|
triggerEvent(n2, 'onPending')
|
2020-09-16 00:45:06 +08:00
|
|
|
// mount pending branch in off-dom container
|
|
|
|
suspense.pendingBranch = newBranch
|
2023-12-12 23:50:28 +08:00
|
|
|
if (newBranch.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
|
|
|
|
suspense.pendingId = newBranch.component!.suspenseId!
|
|
|
|
} else {
|
|
|
|
suspense.pendingId = suspenseId++
|
|
|
|
}
|
2020-09-16 00:45:06 +08:00
|
|
|
patch(
|
|
|
|
null,
|
|
|
|
newBranch,
|
|
|
|
suspense.hiddenContainer,
|
|
|
|
null,
|
|
|
|
parentComponent,
|
|
|
|
suspense,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
2020-09-16 00:45:06 +08:00
|
|
|
)
|
|
|
|
if (suspense.deps <= 0) {
|
|
|
|
// incoming branch has no async deps, resolve now.
|
|
|
|
suspense.resolve()
|
|
|
|
} else {
|
|
|
|
const { timeout, pendingId } = suspense
|
|
|
|
if (timeout > 0) {
|
|
|
|
setTimeout(() => {
|
|
|
|
if (suspense.pendingId === pendingId) {
|
|
|
|
suspense.fallback(newFallback)
|
|
|
|
}
|
|
|
|
}, timeout)
|
|
|
|
} else if (timeout === 0) {
|
|
|
|
suspense.fallback(newFallback)
|
|
|
|
}
|
|
|
|
}
|
2019-10-30 00:30:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-07 23:28:40 +08:00
|
|
|
|
2020-03-23 23:08:22 +08:00
|
|
|
export interface SuspenseBoundary {
|
2020-07-17 23:43:28 +08:00
|
|
|
vnode: VNode<RendererNode, RendererElement, SuspenseProps>
|
2020-03-23 23:08:22 +08:00
|
|
|
parent: SuspenseBoundary | null
|
2019-09-12 05:38:26 +08:00
|
|
|
parentComponent: ComponentInternalInstance | null
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace: ElementNamespace
|
2020-03-23 23:08:22 +08:00
|
|
|
container: RendererElement
|
|
|
|
hiddenContainer: RendererElement
|
2020-09-16 00:45:06 +08:00
|
|
|
activeBranch: VNode | null
|
|
|
|
pendingBranch: VNode | null
|
2019-09-07 23:28:40 +08:00
|
|
|
deps: number
|
2020-09-16 00:45:06 +08:00
|
|
|
pendingId: number
|
|
|
|
timeout: number
|
|
|
|
isInFallback: boolean
|
2020-03-13 10:19:41 +08:00
|
|
|
isHydrating: boolean
|
2019-09-12 01:22:18 +08:00
|
|
|
isUnmounted: boolean
|
2019-09-11 08:53:28 +08:00
|
|
|
effects: Function[]
|
2023-05-08 16:37:46 +08:00
|
|
|
resolve(force?: boolean, sync?: boolean): void
|
2020-09-16 00:45:06 +08:00
|
|
|
fallback(fallbackVNode: VNode): void
|
2020-03-23 23:08:22 +08:00
|
|
|
move(
|
|
|
|
container: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
|
|
|
type: MoveType,
|
|
|
|
): void
|
|
|
|
next(): RendererNode | null
|
2019-10-30 00:30:09 +08:00
|
|
|
registerDep(
|
|
|
|
instance: ComponentInternalInstance,
|
2020-03-23 23:08:22 +08:00
|
|
|
setupRenderEffect: SetupRenderEffectFn,
|
2019-10-30 00:30:09 +08:00
|
|
|
): void
|
2020-03-23 23:08:22 +08:00
|
|
|
unmount(parentSuspense: SuspenseBoundary | null, doRemove?: boolean): void
|
2019-09-07 23:28:40 +08:00
|
|
|
}
|
|
|
|
|
2020-04-25 04:13:44 +08:00
|
|
|
let hasWarned = false
|
|
|
|
|
2020-03-23 23:08:22 +08:00
|
|
|
function createSuspenseBoundary(
|
|
|
|
vnode: VNode,
|
2023-04-21 14:43:30 +08:00
|
|
|
parentSuspense: SuspenseBoundary | null,
|
2019-09-12 05:38:26 +08:00
|
|
|
parentComponent: ComponentInternalInstance | null,
|
2020-03-23 23:08:22 +08:00
|
|
|
container: RendererElement,
|
|
|
|
hiddenContainer: RendererElement,
|
|
|
|
anchor: RendererNode | null,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace: ElementNamespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds: string[] | null,
|
2019-10-30 00:30:09 +08:00
|
|
|
optimized: boolean,
|
2020-03-23 23:08:22 +08:00
|
|
|
rendererInternals: RendererInternals,
|
2020-03-13 10:19:41 +08:00
|
|
|
isHydrating = false,
|
2020-03-23 23:08:22 +08:00
|
|
|
): SuspenseBoundary {
|
2020-04-25 04:13:44 +08:00
|
|
|
/* istanbul ignore if */
|
|
|
|
if (__DEV__ && !__TEST__ && !hasWarned) {
|
|
|
|
hasWarned = true
|
2020-06-09 22:17:42 +08:00
|
|
|
// @ts-expect-error `console.info` cannot be null error
|
2024-01-04 15:54:48 +08:00
|
|
|
// eslint-disable-next-line no-console
|
2020-04-25 04:13:44 +08:00
|
|
|
console[console.info ? 'info' : 'log'](
|
|
|
|
`<Suspense> is an experimental feature and its API will likely change.`,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-10-30 00:30:09 +08:00
|
|
|
const {
|
2020-02-16 00:40:09 +08:00
|
|
|
p: patch,
|
|
|
|
m: move,
|
|
|
|
um: unmount,
|
|
|
|
n: next,
|
2020-09-16 00:45:06 +08:00
|
|
|
o: { parentNode, remove },
|
2019-10-30 00:30:09 +08:00
|
|
|
} = rendererInternals
|
|
|
|
|
2023-04-21 14:43:30 +08:00
|
|
|
// if set `suspensible: true`, set the current suspense as a dep of parent suspense
|
|
|
|
let parentSuspenseId: number | undefined
|
2023-05-08 16:37:46 +08:00
|
|
|
const isSuspensible = isVNodeSuspensible(vnode)
|
2023-04-21 14:43:30 +08:00
|
|
|
if (isSuspensible) {
|
|
|
|
if (parentSuspense?.pendingBranch) {
|
2023-05-08 16:37:46 +08:00
|
|
|
parentSuspenseId = parentSuspense.pendingId
|
2023-04-21 14:43:30 +08:00
|
|
|
parentSuspense.deps++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-14 17:50:56 +08:00
|
|
|
const timeout = vnode.props ? toNumber(vnode.props.timeout) : undefined
|
2022-11-14 16:20:12 +08:00
|
|
|
if (__DEV__) {
|
|
|
|
assertNumber(timeout, `Suspense timeout`)
|
|
|
|
}
|
|
|
|
|
2024-01-08 15:57:14 +08:00
|
|
|
const initialAnchor = anchor
|
2020-03-23 23:08:22 +08:00
|
|
|
const suspense: SuspenseBoundary = {
|
2019-09-10 04:00:50 +08:00
|
|
|
vnode,
|
2023-04-21 14:43:30 +08:00
|
|
|
parent: parentSuspense,
|
2019-09-12 05:38:26 +08:00
|
|
|
parentComponent,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2019-09-10 23:01:11 +08:00
|
|
|
container,
|
2019-09-12 05:38:26 +08:00
|
|
|
hiddenContainer,
|
2019-09-07 23:28:40 +08:00
|
|
|
deps: 0,
|
2024-01-03 17:22:06 +08:00
|
|
|
pendingId: suspenseId++,
|
2020-09-16 00:45:06 +08:00
|
|
|
timeout: typeof timeout === 'number' ? timeout : -1,
|
|
|
|
activeBranch: null,
|
|
|
|
pendingBranch: null,
|
2023-12-08 23:06:34 +08:00
|
|
|
isInFallback: !isHydrating,
|
2020-03-13 10:19:41 +08:00
|
|
|
isHydrating,
|
2019-09-12 01:22:18 +08:00
|
|
|
isUnmounted: false,
|
2019-10-30 00:30:09 +08:00
|
|
|
effects: [],
|
|
|
|
|
2023-05-08 16:37:46 +08:00
|
|
|
resolve(resume = false, sync = false) {
|
2019-10-30 00:30:09 +08:00
|
|
|
if (__DEV__) {
|
2020-09-16 00:45:06 +08:00
|
|
|
if (!resume && !suspense.pendingBranch) {
|
2019-10-30 00:30:09 +08:00
|
|
|
throw new Error(
|
2020-09-16 00:45:06 +08:00
|
|
|
`suspense.resolve() is called without a pending branch.`,
|
2019-10-30 00:30:09 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
if (suspense.isUnmounted) {
|
|
|
|
throw new Error(
|
2020-09-16 00:45:06 +08:00
|
|
|
`suspense.resolve() is called on an already unmounted suspense boundary.`,
|
2019-10-30 00:30:09 +08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const {
|
|
|
|
vnode,
|
2020-09-16 00:45:06 +08:00
|
|
|
activeBranch,
|
|
|
|
pendingBranch,
|
|
|
|
pendingId,
|
2019-10-30 00:30:09 +08:00
|
|
|
effects,
|
|
|
|
parentComponent,
|
|
|
|
container,
|
|
|
|
} = suspense
|
|
|
|
|
2023-10-21 21:24:30 +08:00
|
|
|
// if there's a transition happening we need to wait it to finish.
|
|
|
|
let delayEnter: boolean | null = false
|
2020-03-13 10:19:41 +08:00
|
|
|
if (suspense.isHydrating) {
|
|
|
|
suspense.isHydrating = false
|
2020-09-16 00:45:06 +08:00
|
|
|
} else if (!resume) {
|
2023-10-21 21:24:30 +08:00
|
|
|
delayEnter =
|
2020-09-16 00:45:06 +08:00
|
|
|
activeBranch &&
|
|
|
|
pendingBranch!.transition &&
|
|
|
|
pendingBranch!.transition.mode === 'out-in'
|
|
|
|
if (delayEnter) {
|
|
|
|
activeBranch!.transition!.afterLeave = () => {
|
|
|
|
if (pendingId === suspense.pendingId) {
|
2023-12-04 16:41:55 +08:00
|
|
|
move(
|
|
|
|
pendingBranch!,
|
|
|
|
container,
|
2024-01-08 15:57:14 +08:00
|
|
|
anchor === initialAnchor ? next(activeBranch!) : anchor,
|
2023-12-04 16:41:55 +08:00
|
|
|
MoveType.ENTER,
|
|
|
|
)
|
2023-10-21 21:24:30 +08:00
|
|
|
queuePostFlushCb(effects)
|
2020-09-16 00:45:06 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// unmount current active tree
|
|
|
|
if (activeBranch) {
|
2020-03-13 10:19:41 +08:00
|
|
|
// if the fallback tree was mounted, it may have been moved
|
|
|
|
// as part of a parent suspense. get the latest anchor for insertion
|
2024-01-08 15:57:14 +08:00
|
|
|
// #8105 if `delayEnter` is true, it means that the mounting of
|
|
|
|
// `activeBranch` will be delayed. if the branch switches before
|
|
|
|
// transition completes, both `activeBranch` and `pendingBranch` may
|
|
|
|
// coexist in the `hiddenContainer`. This could result in
|
|
|
|
// `next(activeBranch!)` obtaining an incorrect anchor
|
|
|
|
// (got `pendingBranch.el`).
|
|
|
|
// Therefore, after the mounting of activeBranch is completed,
|
|
|
|
// it is necessary to get the latest anchor.
|
|
|
|
if (parentNode(activeBranch.el!) !== suspense.hiddenContainer) {
|
|
|
|
anchor = next(activeBranch)
|
|
|
|
}
|
2020-09-16 00:45:06 +08:00
|
|
|
unmount(activeBranch, parentComponent, suspense, true)
|
|
|
|
}
|
|
|
|
if (!delayEnter) {
|
|
|
|
// move content from off-dom container to actual container
|
|
|
|
move(pendingBranch!, container, anchor, MoveType.ENTER)
|
2020-03-13 10:19:41 +08:00
|
|
|
}
|
2019-10-30 00:30:09 +08:00
|
|
|
}
|
2020-03-13 10:19:41 +08:00
|
|
|
|
2020-09-16 00:45:06 +08:00
|
|
|
setActiveBranch(suspense, pendingBranch!)
|
|
|
|
suspense.pendingBranch = null
|
|
|
|
suspense.isInFallback = false
|
|
|
|
|
|
|
|
// flush buffered effects
|
2019-10-30 00:30:09 +08:00
|
|
|
// check if there is a pending parent suspense
|
|
|
|
let parent = suspense.parent
|
|
|
|
let hasUnresolvedAncestor = false
|
|
|
|
while (parent) {
|
2020-09-16 00:45:06 +08:00
|
|
|
if (parent.pendingBranch) {
|
2019-10-30 00:30:09 +08:00
|
|
|
// found a pending parent suspense, merge buffered post jobs
|
|
|
|
// into that parent
|
|
|
|
parent.effects.push(...effects)
|
|
|
|
hasUnresolvedAncestor = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
parent = parent.parent
|
|
|
|
}
|
2023-10-21 21:24:30 +08:00
|
|
|
// no pending parent suspense nor transition, flush all jobs
|
|
|
|
if (!hasUnresolvedAncestor && !delayEnter) {
|
2019-10-30 00:30:09 +08:00
|
|
|
queuePostFlushCb(effects)
|
|
|
|
}
|
2020-03-19 03:40:20 +08:00
|
|
|
suspense.effects = []
|
2020-09-16 00:45:06 +08:00
|
|
|
|
2023-04-21 14:43:30 +08:00
|
|
|
// resolve parent suspense if all async deps are resolved
|
|
|
|
if (isSuspensible) {
|
|
|
|
if (
|
|
|
|
parentSuspense &&
|
|
|
|
parentSuspense.pendingBranch &&
|
|
|
|
parentSuspenseId === parentSuspense.pendingId
|
|
|
|
) {
|
|
|
|
parentSuspense.deps--
|
2023-05-08 16:37:46 +08:00
|
|
|
if (parentSuspense.deps === 0 && !sync) {
|
2023-04-21 14:43:30 +08:00
|
|
|
parentSuspense.resolve()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-30 00:30:09 +08:00
|
|
|
// invoke @resolve event
|
2021-06-22 05:03:07 +08:00
|
|
|
triggerEvent(vnode, 'onResolve')
|
2019-10-30 00:30:09 +08:00
|
|
|
},
|
|
|
|
|
2020-09-16 00:45:06 +08:00
|
|
|
fallback(fallbackVNode) {
|
|
|
|
if (!suspense.pendingBranch) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-08 18:25:01 +08:00
|
|
|
const { vnode, activeBranch, parentComponent, container, namespace } =
|
2021-07-20 06:24:18 +08:00
|
|
|
suspense
|
2019-10-30 00:30:09 +08:00
|
|
|
|
2020-09-16 21:30:47 +08:00
|
|
|
// invoke @fallback event
|
2021-06-22 05:03:07 +08:00
|
|
|
triggerEvent(vnode, 'onFallback')
|
2020-09-16 00:45:06 +08:00
|
|
|
|
2023-12-08 12:29:15 +08:00
|
|
|
const anchor = next(activeBranch!)
|
2020-09-16 00:45:06 +08:00
|
|
|
const mountFallback = () => {
|
|
|
|
if (!suspense.isInFallback) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// mount the fallback tree
|
|
|
|
patch(
|
|
|
|
null,
|
|
|
|
fallbackVNode,
|
|
|
|
container,
|
2023-12-08 12:29:15 +08:00
|
|
|
anchor,
|
2020-09-16 00:45:06 +08:00
|
|
|
parentComponent,
|
|
|
|
null, // fallback tree will not have suspense context
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
|
|
|
optimized,
|
2020-09-16 00:45:06 +08:00
|
|
|
)
|
|
|
|
setActiveBranch(suspense, fallbackVNode)
|
|
|
|
}
|
|
|
|
|
|
|
|
const delayEnter =
|
|
|
|
fallbackVNode.transition && fallbackVNode.transition.mode === 'out-in'
|
|
|
|
if (delayEnter) {
|
|
|
|
activeBranch!.transition!.afterLeave = mountFallback
|
|
|
|
}
|
2021-06-22 04:58:43 +08:00
|
|
|
suspense.isInFallback = true
|
|
|
|
|
2020-09-16 00:45:06 +08:00
|
|
|
// unmount current active branch
|
|
|
|
unmount(
|
|
|
|
activeBranch!,
|
2019-10-30 00:30:09 +08:00
|
|
|
parentComponent,
|
2020-09-16 00:45:06 +08:00
|
|
|
null, // no suspense so unmount hooks fire now
|
|
|
|
true, // shouldRemove
|
2019-10-30 00:30:09 +08:00
|
|
|
)
|
|
|
|
|
2020-09-16 00:45:06 +08:00
|
|
|
if (!delayEnter) {
|
|
|
|
mountFallback()
|
2019-10-30 00:30:09 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-11-26 06:34:28 +08:00
|
|
|
move(container, anchor, type) {
|
2020-09-16 00:45:06 +08:00
|
|
|
suspense.activeBranch &&
|
|
|
|
move(suspense.activeBranch, container, anchor, type)
|
2019-10-30 00:40:54 +08:00
|
|
|
suspense.container = container
|
|
|
|
},
|
|
|
|
|
|
|
|
next() {
|
2020-09-16 00:45:06 +08:00
|
|
|
return suspense.activeBranch && next(suspense.activeBranch)
|
2019-10-30 00:40:54 +08:00
|
|
|
},
|
|
|
|
|
2019-10-30 00:30:09 +08:00
|
|
|
registerDep(instance, setupRenderEffect) {
|
2020-11-27 00:06:55 +08:00
|
|
|
const isInPendingSuspense = !!suspense.pendingBranch
|
|
|
|
if (isInPendingSuspense) {
|
|
|
|
suspense.deps++
|
2019-10-30 00:30:09 +08:00
|
|
|
}
|
2020-03-13 10:19:41 +08:00
|
|
|
const hydratedEl = instance.vnode.el
|
2019-10-30 00:30:09 +08:00
|
|
|
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.
|
2020-09-16 00:45:06 +08:00
|
|
|
if (
|
|
|
|
instance.isUnmounted ||
|
|
|
|
suspense.isUnmounted ||
|
|
|
|
suspense.pendingId !== instance.suspenseId
|
|
|
|
) {
|
2019-10-30 00:30:09 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
// retry from this component
|
|
|
|
instance.asyncResolved = true
|
|
|
|
const { vnode } = instance
|
|
|
|
if (__DEV__) {
|
|
|
|
pushWarningContext(vnode)
|
|
|
|
}
|
2020-04-06 06:39:22 +08:00
|
|
|
handleSetupResult(instance, asyncSetupResult, false)
|
2020-03-13 10:19:41 +08:00
|
|
|
if (hydratedEl) {
|
|
|
|
// vnode may have been replaced if an update happened before the
|
2020-05-01 21:42:58 +08:00
|
|
|
// async dep is resolved.
|
2020-03-13 10:19:41 +08:00
|
|
|
vnode.el = hydratedEl
|
|
|
|
}
|
2020-09-16 00:45:06 +08:00
|
|
|
const placeholder = !hydratedEl && instance.subTree.el
|
2019-10-30 00:30:09 +08:00
|
|
|
setupRenderEffect(
|
|
|
|
instance,
|
|
|
|
vnode,
|
2020-03-13 10:19:41 +08:00
|
|
|
// component may have been moved before resolve.
|
|
|
|
// if this is not a hydration, instance.subTree will be the comment
|
|
|
|
// placeholder.
|
2020-09-16 00:45:06 +08:00
|
|
|
parentNode(hydratedEl || instance.subTree.el!)!,
|
2020-03-13 10:19:41 +08: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 12:31:03 +08:00
|
|
|
suspense,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2020-04-07 05:37:47 +08:00
|
|
|
optimized,
|
2019-10-30 00:30:09 +08:00
|
|
|
)
|
2020-09-16 00:45:06 +08:00
|
|
|
if (placeholder) {
|
|
|
|
remove(placeholder)
|
|
|
|
}
|
2019-10-30 00:30:09 +08:00
|
|
|
updateHOCHostEl(instance, vnode.el)
|
|
|
|
if (__DEV__) {
|
|
|
|
popWarningContext()
|
|
|
|
}
|
2020-11-27 00:06:55 +08:00
|
|
|
// only decrease deps count if suspense is not already resolved
|
|
|
|
if (isInPendingSuspense && --suspense.deps === 0) {
|
2019-10-30 00:30:09 +08:00
|
|
|
suspense.resolve()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
unmount(parentSuspense, doRemove) {
|
|
|
|
suspense.isUnmounted = true
|
2020-09-16 00:45:06 +08:00
|
|
|
if (suspense.activeBranch) {
|
|
|
|
unmount(
|
|
|
|
suspense.activeBranch,
|
|
|
|
parentComponent,
|
|
|
|
parentSuspense,
|
|
|
|
doRemove,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (suspense.pendingBranch) {
|
2019-10-30 00:30:09 +08:00
|
|
|
unmount(
|
2020-09-16 00:45:06 +08:00
|
|
|
suspense.pendingBranch,
|
2019-10-30 00:30:09 +08:00
|
|
|
parentComponent,
|
|
|
|
parentSuspense,
|
|
|
|
doRemove,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
2019-09-07 23:28:40 +08:00
|
|
|
}
|
2019-10-30 00:30:09 +08:00
|
|
|
|
|
|
|
return suspense
|
2019-09-07 23:28:40 +08:00
|
|
|
}
|
2019-09-10 23:01:11 +08:00
|
|
|
|
2020-03-13 10:19:41 +08:00
|
|
|
function hydrateSuspense(
|
|
|
|
node: Node,
|
|
|
|
vnode: VNode,
|
|
|
|
parentComponent: ComponentInternalInstance | null,
|
|
|
|
parentSuspense: SuspenseBoundary | null,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace: ElementNamespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds: string[] | null,
|
2020-03-13 10:19:41 +08:00
|
|
|
optimized: boolean,
|
|
|
|
rendererInternals: RendererInternals,
|
|
|
|
hydrateNode: (
|
|
|
|
node: Node,
|
|
|
|
vnode: VNode,
|
|
|
|
parentComponent: ComponentInternalInstance | null,
|
|
|
|
parentSuspense: SuspenseBoundary | null,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds: string[] | null,
|
2020-03-13 10:19:41 +08:00
|
|
|
optimized: boolean,
|
|
|
|
) => Node | null,
|
|
|
|
): Node | null {
|
|
|
|
const suspense = (vnode.suspense = createSuspenseBoundary(
|
|
|
|
vnode,
|
|
|
|
parentSuspense,
|
|
|
|
parentComponent,
|
2020-03-23 23:08:22 +08:00
|
|
|
node.parentNode!,
|
2020-06-11 04:54:23 +08:00
|
|
|
// eslint-disable-next-line no-restricted-globals
|
2020-03-13 10:19:41 +08:00
|
|
|
document.createElement('div'),
|
|
|
|
null,
|
2023-12-08 18:25:01 +08:00
|
|
|
namespace,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
2020-03-13 10:19:41 +08:00
|
|
|
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,
|
2020-09-16 00:45:06 +08:00
|
|
|
(suspense.pendingBranch = vnode.ssContent!),
|
2020-03-13 10:19:41 +08:00
|
|
|
parentComponent,
|
|
|
|
suspense,
|
2021-03-06 00:10:06 +08:00
|
|
|
slotScopeIds,
|
2020-03-13 10:19:41 +08:00
|
|
|
optimized,
|
|
|
|
)
|
|
|
|
if (suspense.deps === 0) {
|
2023-05-11 15:01:33 +08:00
|
|
|
suspense.resolve(false, true)
|
2020-03-13 10:19:41 +08:00
|
|
|
}
|
|
|
|
return result
|
2020-06-11 04:54:23 +08:00
|
|
|
/* eslint-enable no-restricted-globals */
|
2020-03-13 10:19:41 +08:00
|
|
|
}
|
|
|
|
|
2021-05-28 04:32:31 +08:00
|
|
|
function normalizeSuspenseChildren(vnode: VNode) {
|
2019-09-10 23:17:26 +08:00
|
|
|
const { shapeFlag, children } = vnode
|
2021-05-28 04:32:31 +08:00
|
|
|
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)
|
2020-09-16 00:45:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function normalizeSuspenseSlot(s: any) {
|
2021-05-28 04:32:31 +08:00
|
|
|
let block: VNode[] | null | undefined
|
2020-09-16 00:45:06 +08:00
|
|
|
if (isFunction(s)) {
|
2021-09-17 00:14:33 +08:00
|
|
|
const trackBlock = isBlockTreeEnabled && s._c
|
|
|
|
if (trackBlock) {
|
2021-05-28 04:32:31 +08:00
|
|
|
// disableTracking: false
|
|
|
|
// allow block tracking for compiled slots
|
|
|
|
// (see ./componentRenderContext.ts)
|
|
|
|
s._d = false
|
|
|
|
openBlock()
|
|
|
|
}
|
2020-09-16 00:45:06 +08:00
|
|
|
s = s()
|
2021-09-17 00:14:33 +08:00
|
|
|
if (trackBlock) {
|
2021-05-28 04:32:31 +08:00
|
|
|
s._d = true
|
|
|
|
block = currentBlock
|
|
|
|
closeBlock()
|
|
|
|
}
|
2020-09-16 00:45:06 +08:00
|
|
|
}
|
|
|
|
if (isArray(s)) {
|
|
|
|
const singleChild = filterSingleRoot(s)
|
2023-11-06 17:48:40 +08:00
|
|
|
if (
|
|
|
|
__DEV__ &&
|
|
|
|
!singleChild &&
|
|
|
|
s.filter(child => child !== NULL_DYNAMIC_COMPONENT).length > 0
|
|
|
|
) {
|
2020-09-16 00:45:06 +08:00
|
|
|
warn(`<Suspense> slots expect a single root node.`)
|
2019-10-13 10:52:11 +08:00
|
|
|
}
|
2020-09-16 00:45:06 +08:00
|
|
|
s = singleChild
|
2019-09-10 23:01:11 +08:00
|
|
|
}
|
2021-05-28 04:32:31 +08:00
|
|
|
s = normalizeVNode(s)
|
2021-07-29 04:49:34 +08:00
|
|
|
if (block && !s.dynamicChildren) {
|
2021-05-28 04:32:31 +08:00
|
|
|
s.dynamicChildren = block.filter(c => c !== s)
|
|
|
|
}
|
|
|
|
return s
|
2019-09-10 23:01:11 +08:00
|
|
|
}
|
2019-10-30 00:40:54 +08:00
|
|
|
|
|
|
|
export function queueEffectWithSuspense(
|
|
|
|
fn: Function | Function[],
|
|
|
|
suspense: SuspenseBoundary | null,
|
|
|
|
): void {
|
2020-09-16 00:45:06 +08:00
|
|
|
if (suspense && suspense.pendingBranch) {
|
2019-10-30 00:40:54 +08:00
|
|
|
if (isArray(fn)) {
|
|
|
|
suspense.effects.push(...fn)
|
|
|
|
} else {
|
|
|
|
suspense.effects.push(fn)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
queuePostFlushCb(fn)
|
|
|
|
}
|
|
|
|
}
|
2020-09-16 00:45:06 +08:00
|
|
|
|
|
|
|
function setActiveBranch(suspense: SuspenseBoundary, branch: VNode) {
|
|
|
|
suspense.activeBranch = branch
|
|
|
|
const { vnode, parentComponent } = suspense
|
2024-01-10 21:03:20 +08:00
|
|
|
let el = branch.el
|
|
|
|
// if branch has no el after patch, it's a HOC wrapping async components
|
|
|
|
// drill and locate the placeholder comment node
|
|
|
|
while (!el && branch.component) {
|
|
|
|
branch = branch.component.subTree
|
|
|
|
el = branch.el
|
|
|
|
}
|
|
|
|
vnode.el = el
|
2020-09-16 00:45:06 +08:00
|
|
|
// in case suspense is the root node of a component,
|
|
|
|
// recursively update the HOC el
|
|
|
|
if (parentComponent && parentComponent.subTree === vnode) {
|
|
|
|
parentComponent.vnode.el = el
|
|
|
|
updateHOCHostEl(parentComponent, el)
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 16:37:46 +08:00
|
|
|
|
|
|
|
function isVNodeSuspensible(vnode: VNode) {
|
|
|
|
return vnode.props?.suspensible != null && vnode.props.suspensible !== false
|
|
|
|
}
|