refactor(suspense): make suspense tree-shakeable
This commit is contained in:
parent
5cce23f4c6
commit
17d71fa407
@ -149,3 +149,13 @@ function hasPropsChanged(prevProps: Data, nextProps: Data): boolean {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateHOCHostEl(
|
||||||
|
{ vnode, parent }: ComponentInternalInstance,
|
||||||
|
el: object // HostNode
|
||||||
|
) {
|
||||||
|
while (parent && parent.subTree === vnode) {
|
||||||
|
;(vnode = parent.vnode).el = el
|
||||||
|
parent = parent.parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,20 +6,19 @@ import {
|
|||||||
normalizeVNode,
|
normalizeVNode,
|
||||||
VNode,
|
VNode,
|
||||||
VNodeChildren,
|
VNodeChildren,
|
||||||
Suspense,
|
|
||||||
createVNode
|
createVNode
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
setupStatefulComponent,
|
setupStatefulComponent,
|
||||||
handleSetupResult,
|
|
||||||
Component,
|
Component,
|
||||||
Data
|
Data
|
||||||
} from './component'
|
} from './component'
|
||||||
import {
|
import {
|
||||||
renderComponentRoot,
|
renderComponentRoot,
|
||||||
shouldUpdateComponent
|
shouldUpdateComponent,
|
||||||
|
updateHOCHostEl
|
||||||
} from './componentRenderUtils'
|
} from './componentRenderUtils'
|
||||||
import {
|
import {
|
||||||
isString,
|
isString,
|
||||||
@ -47,51 +46,8 @@ import { pushWarningContext, popWarningContext, warn } from './warning'
|
|||||||
import { invokeDirectiveHook } from './directives'
|
import { invokeDirectiveHook } from './directives'
|
||||||
import { ComponentPublicInstance } from './componentProxy'
|
import { ComponentPublicInstance } from './componentProxy'
|
||||||
import { App, createAppAPI } from './apiApp'
|
import { App, createAppAPI } from './apiApp'
|
||||||
import {
|
import { SuspenseBoundary, SuspenseImpl } from './suspense'
|
||||||
SuspenseBoundary,
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
createSuspenseBoundary,
|
|
||||||
normalizeSuspenseChildren
|
|
||||||
} from './suspense'
|
|
||||||
import { handleError, ErrorCodes, callWithErrorHandling } from './errorHandling'
|
|
||||||
|
|
||||||
const prodEffectOptions = {
|
|
||||||
scheduler: queueJob
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDevEffectOptions(
|
|
||||||
instance: ComponentInternalInstance
|
|
||||||
): ReactiveEffectOptions {
|
|
||||||
return {
|
|
||||||
scheduler: queueJob,
|
|
||||||
onTrack: instance.rtc ? e => invokeHooks(instance.rtc!, e) : void 0,
|
|
||||||
onTrigger: instance.rtg ? e => invokeHooks(instance.rtg!, e) : void 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSameType(n1: VNode, n2: VNode): boolean {
|
|
||||||
return n1.type === n2.type && n1.key === n2.key
|
|
||||||
}
|
|
||||||
|
|
||||||
function invokeHooks(hooks: Function[], arg?: DebuggerEvent) {
|
|
||||||
for (let i = 0; i < hooks.length; i++) {
|
|
||||||
hooks[i](arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queuePostRenderEffect(
|
|
||||||
fn: Function | Function[],
|
|
||||||
suspense: SuspenseBoundary<any, any> | null
|
|
||||||
) {
|
|
||||||
if (suspense !== null && !suspense.isResolved) {
|
|
||||||
if (isArray(fn)) {
|
|
||||||
suspense.effects.push(...fn)
|
|
||||||
} else {
|
|
||||||
suspense.effects.push(fn)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
queuePostFlushCb(fn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RendererOptions<HostNode = any, HostElement = any> {
|
export interface RendererOptions<HostNode = any, HostElement = any> {
|
||||||
patchProp(
|
patchProp(
|
||||||
@ -126,6 +82,75 @@ export type RootRenderFunction<HostNode, HostElement> = (
|
|||||||
dom: HostElement
|
dom: HostElement
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
|
// An object exposing the internals of a renderer, passed to tree-shakeable
|
||||||
|
// features so that they can be decoupled from this file.
|
||||||
|
export interface RendererInternals<HostNode = any, HostElement = any> {
|
||||||
|
patch: (
|
||||||
|
n1: VNode<HostNode, HostElement> | null, // null means this is a mount
|
||||||
|
n2: VNode<HostNode, HostElement>,
|
||||||
|
container: HostElement,
|
||||||
|
anchor?: HostNode | null,
|
||||||
|
parentComponent?: ComponentInternalInstance | null,
|
||||||
|
parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null,
|
||||||
|
isSVG?: boolean,
|
||||||
|
optimized?: boolean
|
||||||
|
) => void
|
||||||
|
unmount: (
|
||||||
|
vnode: VNode<HostNode, HostElement>,
|
||||||
|
parentComponent: ComponentInternalInstance | null,
|
||||||
|
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
|
||||||
|
doRemove?: boolean
|
||||||
|
) => void
|
||||||
|
move: (
|
||||||
|
vnode: VNode<HostNode, HostElement>,
|
||||||
|
container: HostElement,
|
||||||
|
anchor: HostNode | null
|
||||||
|
) => void
|
||||||
|
next: (vnode: VNode<HostNode, HostElement>) => HostNode | null
|
||||||
|
options: RendererOptions<HostNode, HostElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
const prodEffectOptions = {
|
||||||
|
scheduler: queueJob
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDevEffectOptions(
|
||||||
|
instance: ComponentInternalInstance
|
||||||
|
): ReactiveEffectOptions {
|
||||||
|
return {
|
||||||
|
scheduler: queueJob,
|
||||||
|
onTrack: instance.rtc ? e => invokeHooks(instance.rtc!, e) : void 0,
|
||||||
|
onTrigger: instance.rtg ? e => invokeHooks(instance.rtg!, e) : void 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSameType(n1: VNode, n2: VNode): boolean {
|
||||||
|
return n1.type === n2.type && n1.key === n2.key
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokeHooks(hooks: Function[], arg?: DebuggerEvent) {
|
||||||
|
for (let i = 0; i < hooks.length; i++) {
|
||||||
|
hooks[i](arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const queuePostRenderEffect = __FEATURE_SUSPENSE__
|
||||||
|
? (
|
||||||
|
fn: Function | Function[],
|
||||||
|
suspense: SuspenseBoundary<any, any> | null
|
||||||
|
) => {
|
||||||
|
if (suspense !== null && !suspense.isResolved) {
|
||||||
|
if (isArray(fn)) {
|
||||||
|
suspense.effects.push(...fn)
|
||||||
|
} else {
|
||||||
|
suspense.effects.push(fn)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queuePostFlushCb(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: queuePostFlushCb
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The createRenderer function accepts two generic arguments:
|
* The createRenderer function accepts two generic arguments:
|
||||||
* HostNode and HostElement, corresponding to Node and Element types in the
|
* HostNode and HostElement, corresponding to Node and Element types in the
|
||||||
@ -168,6 +193,14 @@ export function createRenderer<
|
|||||||
querySelector: hostQuerySelector
|
querySelector: hostQuerySelector
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
|
const internals: RendererInternals<HostNode, HostElement> = {
|
||||||
|
patch,
|
||||||
|
unmount,
|
||||||
|
move,
|
||||||
|
next: getNextHostNode,
|
||||||
|
options
|
||||||
|
}
|
||||||
|
|
||||||
function patch(
|
function patch(
|
||||||
n1: HostVNode | null, // null means this is a mount
|
n1: HostVNode | null, // null means this is a mount
|
||||||
n2: HostVNode,
|
n2: HostVNode,
|
||||||
@ -217,22 +250,6 @@ export function createRenderer<
|
|||||||
optimized
|
optimized
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case Suspense:
|
|
||||||
if (__FEATURE_SUSPENSE__) {
|
|
||||||
processSuspense(
|
|
||||||
n1,
|
|
||||||
n2,
|
|
||||||
container,
|
|
||||||
anchor,
|
|
||||||
parentComponent,
|
|
||||||
parentSuspense,
|
|
||||||
isSVG,
|
|
||||||
optimized
|
|
||||||
)
|
|
||||||
} else if (__DEV__) {
|
|
||||||
warn(`Suspense is not enabled in the version of Vue you are using.`)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
if (shapeFlag & ShapeFlags.ELEMENT) {
|
if (shapeFlag & ShapeFlags.ELEMENT) {
|
||||||
processElement(
|
processElement(
|
||||||
@ -256,6 +273,18 @@ export function createRenderer<
|
|||||||
isSVG,
|
isSVG,
|
||||||
optimized
|
optimized
|
||||||
)
|
)
|
||||||
|
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
|
||||||
|
;(type as typeof SuspenseImpl).process(
|
||||||
|
n1,
|
||||||
|
n2,
|
||||||
|
container,
|
||||||
|
anchor,
|
||||||
|
parentComponent,
|
||||||
|
parentSuspense,
|
||||||
|
isSVG,
|
||||||
|
optimized,
|
||||||
|
internals
|
||||||
|
)
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
warn('Invalid HostVNode type:', n2.type, `(${typeof n2.type})`)
|
warn('Invalid HostVNode type:', n2.type, `(${typeof n2.type})`)
|
||||||
}
|
}
|
||||||
@ -725,260 +754,6 @@ export function createRenderer<
|
|||||||
processCommentNode(n1, n2, container, anchor)
|
processCommentNode(n1, n2, container, anchor)
|
||||||
}
|
}
|
||||||
|
|
||||||
function processSuspense(
|
|
||||||
n1: HostVNode | null,
|
|
||||||
n2: HostVNode,
|
|
||||||
container: HostElement,
|
|
||||||
anchor: HostNode | null,
|
|
||||||
parentComponent: ComponentInternalInstance | null,
|
|
||||||
parentSuspense: HostSuspenseBoundary | null,
|
|
||||||
isSVG: boolean,
|
|
||||||
optimized: boolean
|
|
||||||
) {
|
|
||||||
if (n1 == null) {
|
|
||||||
mountSuspense(
|
|
||||||
n2,
|
|
||||||
container,
|
|
||||||
anchor,
|
|
||||||
parentComponent,
|
|
||||||
parentSuspense,
|
|
||||||
isSVG,
|
|
||||||
optimized
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
patchSuspense(
|
|
||||||
n1,
|
|
||||||
n2,
|
|
||||||
container,
|
|
||||||
anchor,
|
|
||||||
parentComponent,
|
|
||||||
isSVG,
|
|
||||||
optimized
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mountSuspense(
|
|
||||||
n2: HostVNode,
|
|
||||||
container: HostElement,
|
|
||||||
anchor: HostNode | null,
|
|
||||||
parentComponent: ComponentInternalInstance | null,
|
|
||||||
parentSuspense: HostSuspenseBoundary | null,
|
|
||||||
isSVG: boolean,
|
|
||||||
optimized: boolean
|
|
||||||
) {
|
|
||||||
const hiddenContainer = hostCreateElement('div')
|
|
||||||
const suspense = (n2.suspense = createSuspenseBoundary(
|
|
||||||
n2,
|
|
||||||
parentSuspense,
|
|
||||||
parentComponent,
|
|
||||||
container,
|
|
||||||
hiddenContainer,
|
|
||||||
anchor,
|
|
||||||
isSVG,
|
|
||||||
optimized
|
|
||||||
))
|
|
||||||
|
|
||||||
const { content, fallback } = normalizeSuspenseChildren(n2)
|
|
||||||
suspense.subTree = content
|
|
||||||
suspense.fallbackTree = fallback
|
|
||||||
|
|
||||||
// start mounting the content subtree in an off-dom container
|
|
||||||
patch(
|
|
||||||
null,
|
|
||||||
content,
|
|
||||||
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,
|
|
||||||
fallback,
|
|
||||||
container,
|
|
||||||
anchor,
|
|
||||||
parentComponent,
|
|
||||||
null, // fallback tree will not have suspense context
|
|
||||||
isSVG,
|
|
||||||
optimized
|
|
||||||
)
|
|
||||||
n2.el = fallback.el
|
|
||||||
} else {
|
|
||||||
// Suspense has no async deps. Just resolve.
|
|
||||||
resolveSuspense(suspense)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function patchSuspense(
|
|
||||||
n1: HostVNode,
|
|
||||||
n2: HostVNode,
|
|
||||||
container: HostElement,
|
|
||||||
anchor: HostNode | null,
|
|
||||||
parentComponent: ComponentInternalInstance | null,
|
|
||||||
isSVG: boolean,
|
|
||||||
optimized: boolean
|
|
||||||
) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveSuspense(suspense: HostSuspenseBoundary) {
|
|
||||||
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
|
|
||||||
|
|
||||||
// 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 = getNextHostNode(fallbackTree)
|
|
||||||
unmount(fallbackTree as HostVNode, parentComponent, suspense, true)
|
|
||||||
}
|
|
||||||
// move content from off-dom container to actual container
|
|
||||||
move(subTree as HostVNode, container, anchor)
|
|
||||||
const el = (vnode.el = (subTree as HostVNode).el!)
|
|
||||||
// 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
|
|
||||||
// invoke @resolve event
|
|
||||||
const onResolve = vnode.props && vnode.props.onResolve
|
|
||||||
if (isFunction(onResolve)) {
|
|
||||||
onResolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function restartSuspense(suspense: HostSuspenseBoundary) {
|
|
||||||
suspense.isResolved = false
|
|
||||||
const {
|
|
||||||
vnode,
|
|
||||||
subTree,
|
|
||||||
fallbackTree,
|
|
||||||
parentComponent,
|
|
||||||
container,
|
|
||||||
hiddenContainer,
|
|
||||||
isSVG,
|
|
||||||
optimized
|
|
||||||
} = suspense
|
|
||||||
|
|
||||||
// move content tree back to the off-dom container
|
|
||||||
const anchor = getNextHostNode(subTree)
|
|
||||||
move(subTree as HostVNode, hiddenContainer, null)
|
|
||||||
// remount the fallback tree
|
|
||||||
patch(
|
|
||||||
null,
|
|
||||||
fallbackTree,
|
|
||||||
container,
|
|
||||||
anchor,
|
|
||||||
parentComponent,
|
|
||||||
null, // fallback tree will not have suspense context
|
|
||||||
isSVG,
|
|
||||||
optimized
|
|
||||||
)
|
|
||||||
const el = (vnode.el = (fallbackTree as HostVNode).el!)
|
|
||||||
// suspense as the root node of a component...
|
|
||||||
if (parentComponent && parentComponent.subTree === vnode) {
|
|
||||||
parentComponent.vnode.el = el
|
|
||||||
updateHOCHostEl(parentComponent, el)
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke @suspense event
|
|
||||||
const onSuspense = vnode.props && vnode.props.onSuspense
|
|
||||||
if (isFunction(onSuspense)) {
|
|
||||||
onSuspense()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function processComponent(
|
function processComponent(
|
||||||
n1: HostVNode | null,
|
n1: HostVNode | null,
|
||||||
n2: HostVNode,
|
n2: HostVNode,
|
||||||
@ -1066,34 +841,10 @@ export function createRenderer<
|
|||||||
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
|
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
|
||||||
if (!parentSuspense) {
|
if (!parentSuspense) {
|
||||||
// TODO handle this properly
|
// TODO handle this properly
|
||||||
throw new Error('Async component without a suspense boundary!')
|
throw new Error('Async setup() is used without a suspense boundary!')
|
||||||
}
|
}
|
||||||
|
|
||||||
// parent suspense already resolved, need to re-suspense
|
parentSuspense.registerDep(instance, setupRenderEffect)
|
||||||
// use queueJob so it's handled synchronously after patching the current
|
|
||||||
// suspense tree
|
|
||||||
if (parentSuspense.isResolved) {
|
|
||||||
queueJob(() => {
|
|
||||||
restartSuspense(parentSuspense)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
parentSuspense.deps++
|
|
||||||
instance.asyncDep
|
|
||||||
.catch(err => {
|
|
||||||
handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
|
|
||||||
})
|
|
||||||
.then(asyncSetupResult => {
|
|
||||||
// component may be unmounted before resolve
|
|
||||||
if (!instance.isUnmounted && !parentSuspense.isUnmounted) {
|
|
||||||
retryAsyncComponent(
|
|
||||||
instance,
|
|
||||||
asyncSetupResult,
|
|
||||||
parentSuspense,
|
|
||||||
isSVG
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// give it a placeholder
|
// give it a placeholder
|
||||||
const placeholder = (instance.subTree = createVNode(Comment))
|
const placeholder = (instance.subTree = createVNode(Comment))
|
||||||
@ -1116,38 +867,6 @@ export function createRenderer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function retryAsyncComponent(
|
|
||||||
instance: ComponentInternalInstance,
|
|
||||||
asyncSetupResult: unknown,
|
|
||||||
parentSuspense: HostSuspenseBoundary,
|
|
||||||
isSVG: boolean
|
|
||||||
) {
|
|
||||||
parentSuspense.deps--
|
|
||||||
// retry from this component
|
|
||||||
instance.asyncResolved = true
|
|
||||||
const { vnode } = instance
|
|
||||||
if (__DEV__) {
|
|
||||||
pushWarningContext(vnode)
|
|
||||||
}
|
|
||||||
handleSetupResult(instance, asyncSetupResult, parentSuspense)
|
|
||||||
setupRenderEffect(
|
|
||||||
instance,
|
|
||||||
parentSuspense,
|
|
||||||
vnode,
|
|
||||||
// component may have been moved before resolve
|
|
||||||
hostParentNode(instance.subTree.el) as HostElement,
|
|
||||||
getNextHostNode(instance.subTree),
|
|
||||||
isSVG
|
|
||||||
)
|
|
||||||
updateHOCHostEl(instance, vnode.el as HostNode)
|
|
||||||
if (__DEV__) {
|
|
||||||
popWarningContext()
|
|
||||||
}
|
|
||||||
if (parentSuspense.deps === 0) {
|
|
||||||
resolveSuspense(parentSuspense)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupRenderEffect(
|
function setupRenderEffect(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
parentSuspense: HostSuspenseBoundary | null,
|
parentSuspense: HostSuspenseBoundary | null,
|
||||||
@ -1237,16 +956,6 @@ export function createRenderer<
|
|||||||
resolveSlots(instance, nextVNode.children)
|
resolveSlots(instance, nextVNode.children)
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHOCHostEl(
|
|
||||||
{ vnode, parent }: ComponentInternalInstance,
|
|
||||||
el: HostNode
|
|
||||||
) {
|
|
||||||
while (parent && parent.subTree === vnode) {
|
|
||||||
;(vnode = parent.vnode).el = el
|
|
||||||
parent = parent.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function patchChildren(
|
function patchChildren(
|
||||||
n1: HostVNode | null,
|
n1: HostVNode | null,
|
||||||
n2: HostVNode,
|
n2: HostVNode,
|
||||||
@ -1640,11 +1349,11 @@ export function createRenderer<
|
|||||||
container: HostElement,
|
container: HostElement,
|
||||||
anchor: HostNode | null
|
anchor: HostNode | null
|
||||||
) {
|
) {
|
||||||
if (vnode.component !== null) {
|
if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
|
||||||
move(vnode.component.subTree, container, anchor)
|
move(vnode.component!.subTree, container, anchor)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (__FEATURE_SUSPENSE__ && vnode.type === Suspense) {
|
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
||||||
const suspense = vnode.suspense!
|
const suspense = vnode.suspense!
|
||||||
move(
|
move(
|
||||||
suspense.isResolved ? suspense.subTree : suspense.fallbackTree,
|
suspense.isResolved ? suspense.subTree : suspense.fallbackTree,
|
||||||
@ -1676,8 +1385,6 @@ export function createRenderer<
|
|||||||
props,
|
props,
|
||||||
ref,
|
ref,
|
||||||
type,
|
type,
|
||||||
component,
|
|
||||||
suspense,
|
|
||||||
children,
|
children,
|
||||||
dynamicChildren,
|
dynamicChildren,
|
||||||
shapeFlag,
|
shapeFlag,
|
||||||
@ -1689,13 +1396,13 @@ export function createRenderer<
|
|||||||
setRef(ref, null, parentComponent, null)
|
setRef(ref, null, parentComponent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component != null) {
|
if (shapeFlag & ShapeFlags.COMPONENT) {
|
||||||
unmountComponent(component, parentSuspense, doRemove)
|
unmountComponent(vnode.component!, parentSuspense, doRemove)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__FEATURE_SUSPENSE__ && suspense != null) {
|
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
|
||||||
unmountSuspense(suspense, parentComponent, parentSuspense, doRemove)
|
vnode.suspense!.unmount(parentSuspense, doRemove)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1774,24 +1481,11 @@ export function createRenderer<
|
|||||||
) {
|
) {
|
||||||
parentSuspense.deps--
|
parentSuspense.deps--
|
||||||
if (parentSuspense.deps === 0) {
|
if (parentSuspense.deps === 0) {
|
||||||
resolveSuspense(parentSuspense)
|
parentSuspense.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unmountSuspense(
|
|
||||||
suspense: HostSuspenseBoundary,
|
|
||||||
parentComponent: ComponentInternalInstance | null,
|
|
||||||
parentSuspense: HostSuspenseBoundary | null,
|
|
||||||
doRemove?: boolean
|
|
||||||
) {
|
|
||||||
suspense.isUnmounted = true
|
|
||||||
unmount(suspense.subTree, parentComponent, parentSuspense, doRemove)
|
|
||||||
if (!suspense.isResolved) {
|
|
||||||
unmount(suspense.fallbackTree, parentComponent, parentSuspense, doRemove)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function unmountChildren(
|
function unmountChildren(
|
||||||
children: HostVNode[],
|
children: HostVNode[],
|
||||||
parentComponent: ComponentInternalInstance | null,
|
parentComponent: ComponentInternalInstance | null,
|
||||||
@ -1804,21 +1498,17 @@ export function createRenderer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNextHostNode({
|
function getNextHostNode(vnode: HostVNode): HostNode | null {
|
||||||
component,
|
if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
|
||||||
suspense,
|
return getNextHostNode(vnode.component!.subTree)
|
||||||
anchor,
|
|
||||||
el
|
|
||||||
}: HostVNode): HostNode | null {
|
|
||||||
if (component !== null) {
|
|
||||||
return getNextHostNode(component.subTree)
|
|
||||||
}
|
}
|
||||||
if (__FEATURE_SUSPENSE__ && suspense !== null) {
|
if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
|
||||||
|
const suspense = vnode.suspense!
|
||||||
return getNextHostNode(
|
return getNextHostNode(
|
||||||
suspense.isResolved ? suspense.subTree : suspense.fallbackTree
|
suspense.isResolved ? suspense.subTree : suspense.fallbackTree
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return hostNextSibling((anchor || el)!)
|
return hostNextSibling((vnode.anchor || vnode.el)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRef(
|
function setRef(
|
||||||
|
@ -7,6 +7,7 @@ export const enum ShapeFlags {
|
|||||||
TEXT_CHILDREN = 1 << 3,
|
TEXT_CHILDREN = 1 << 3,
|
||||||
ARRAY_CHILDREN = 1 << 4,
|
ARRAY_CHILDREN = 1 << 4,
|
||||||
SLOTS_CHILDREN = 1 << 5,
|
SLOTS_CHILDREN = 1 << 5,
|
||||||
|
SUSPENSE = 1 << 6,
|
||||||
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
|
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,5 +19,6 @@ export const PublicShapeFlags = {
|
|||||||
TEXT_CHILDREN: ShapeFlags.TEXT_CHILDREN,
|
TEXT_CHILDREN: ShapeFlags.TEXT_CHILDREN,
|
||||||
ARRAY_CHILDREN: ShapeFlags.ARRAY_CHILDREN,
|
ARRAY_CHILDREN: ShapeFlags.ARRAY_CHILDREN,
|
||||||
SLOTS_CHILDREN: ShapeFlags.SLOTS_CHILDREN,
|
SLOTS_CHILDREN: ShapeFlags.SLOTS_CHILDREN,
|
||||||
|
SUSPENSE: ShapeFlags.SUSPENSE,
|
||||||
COMPONENT: ShapeFlags.COMPONENT
|
COMPONENT: ShapeFlags.COMPONENT
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,180 @@
|
|||||||
import { VNode, normalizeVNode, VNodeChild } from './vnode'
|
import { VNode, normalizeVNode, VNodeChild, VNodeTypes } from './vnode'
|
||||||
import { ShapeFlags } from '.'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { isFunction } from '@vue/shared'
|
import { isFunction } from '@vue/shared'
|
||||||
import { ComponentInternalInstance } from './component'
|
import { ComponentInternalInstance, handleSetupResult } from './component'
|
||||||
import { Slots } from './componentSlots'
|
import { Slots } from './componentSlots'
|
||||||
|
import { RendererInternals } from './createRenderer'
|
||||||
|
import { queuePostFlushCb, queueJob } from './scheduler'
|
||||||
|
import { updateHOCHostEl } from './componentRenderUtils'
|
||||||
|
import { handleError, ErrorCodes } from './errorHandling'
|
||||||
|
import { pushWarningContext, popWarningContext } from './warning'
|
||||||
|
|
||||||
export const SuspenseSymbol = Symbol(__DEV__ ? 'Suspense key' : undefined)
|
export function isSuspenseType(type: VNodeTypes): type is typeof SuspenseImpl {
|
||||||
|
return (type as any).__isSuspenseImpl === true
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SuspenseImpl = {
|
||||||
|
__isSuspenseImpl: true,
|
||||||
|
process(
|
||||||
|
n1: VNode | null,
|
||||||
|
n2: VNode,
|
||||||
|
container: object,
|
||||||
|
anchor: object | null,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountSuspense(
|
||||||
|
n2: VNode,
|
||||||
|
container: object,
|
||||||
|
anchor: object | null,
|
||||||
|
parentComponent: ComponentInternalInstance | null,
|
||||||
|
parentSuspense: SuspenseBoundary | null,
|
||||||
|
isSVG: boolean,
|
||||||
|
optimized: boolean,
|
||||||
|
rendererInternals: RendererInternals
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
patch,
|
||||||
|
options: { createElement }
|
||||||
|
} = rendererInternals
|
||||||
|
const hiddenContainer = createElement('div')
|
||||||
|
const suspense = (n2.suspense = createSuspenseBoundary(
|
||||||
|
n2,
|
||||||
|
parentSuspense,
|
||||||
|
parentComponent,
|
||||||
|
container,
|
||||||
|
hiddenContainer,
|
||||||
|
anchor,
|
||||||
|
isSVG,
|
||||||
|
optimized,
|
||||||
|
rendererInternals
|
||||||
|
))
|
||||||
|
|
||||||
|
const { content, fallback } = normalizeSuspenseChildren(n2)
|
||||||
|
suspense.subTree = content
|
||||||
|
suspense.fallbackTree = fallback
|
||||||
|
|
||||||
|
// start mounting the content subtree in an off-dom container
|
||||||
|
patch(
|
||||||
|
null,
|
||||||
|
content,
|
||||||
|
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,
|
||||||
|
fallback,
|
||||||
|
container,
|
||||||
|
anchor,
|
||||||
|
parentComponent,
|
||||||
|
null, // fallback tree will not have suspense context
|
||||||
|
isSVG,
|
||||||
|
optimized
|
||||||
|
)
|
||||||
|
n2.el = fallback.el
|
||||||
|
} else {
|
||||||
|
// Suspense has no async deps. Just resolve.
|
||||||
|
suspense.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchSuspense(
|
||||||
|
n1: VNode,
|
||||||
|
n2: VNode,
|
||||||
|
container: object,
|
||||||
|
anchor: object | null,
|
||||||
|
parentComponent: ComponentInternalInstance | null,
|
||||||
|
isSVG: boolean,
|
||||||
|
optimized: boolean,
|
||||||
|
{ patch }: RendererInternals
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
export interface SuspenseBoundary<
|
export interface SuspenseBoundary<
|
||||||
HostNode = any,
|
HostNode = any,
|
||||||
@ -25,9 +195,26 @@ export interface SuspenseBoundary<
|
|||||||
isResolved: boolean
|
isResolved: boolean
|
||||||
isUnmounted: boolean
|
isUnmounted: boolean
|
||||||
effects: Function[]
|
effects: Function[]
|
||||||
|
resolve(): void
|
||||||
|
restart(): void
|
||||||
|
registerDep(
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
setupRenderEffect: (
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
|
||||||
|
initialVNode: VNode<HostNode, HostElement>,
|
||||||
|
container: HostElement,
|
||||||
|
anchor: HostNode | null,
|
||||||
|
isSVG: boolean
|
||||||
|
) => void
|
||||||
|
): void
|
||||||
|
unmount(
|
||||||
|
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
|
||||||
|
doRemove?: boolean
|
||||||
|
): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSuspenseBoundary<HostNode, HostElement>(
|
function createSuspenseBoundary<HostNode, HostElement>(
|
||||||
vnode: VNode<HostNode, HostElement>,
|
vnode: VNode<HostNode, HostElement>,
|
||||||
parent: SuspenseBoundary<HostNode, HostElement> | null,
|
parent: SuspenseBoundary<HostNode, HostElement> | null,
|
||||||
parentComponent: ComponentInternalInstance | null,
|
parentComponent: ComponentInternalInstance | null,
|
||||||
@ -35,9 +222,18 @@ export function createSuspenseBoundary<HostNode, HostElement>(
|
|||||||
hiddenContainer: HostElement,
|
hiddenContainer: HostElement,
|
||||||
anchor: HostNode | null,
|
anchor: HostNode | null,
|
||||||
isSVG: boolean,
|
isSVG: boolean,
|
||||||
optimized: boolean
|
optimized: boolean,
|
||||||
|
rendererInternals: RendererInternals<HostNode, HostElement>
|
||||||
): SuspenseBoundary<HostNode, HostElement> {
|
): SuspenseBoundary<HostNode, HostElement> {
|
||||||
return {
|
const {
|
||||||
|
patch,
|
||||||
|
move,
|
||||||
|
unmount,
|
||||||
|
next,
|
||||||
|
options: { parentNode }
|
||||||
|
} = rendererInternals
|
||||||
|
|
||||||
|
const suspense: SuspenseBoundary<HostNode, HostElement> = {
|
||||||
vnode,
|
vnode,
|
||||||
parent,
|
parent,
|
||||||
parentComponent,
|
parentComponent,
|
||||||
@ -51,11 +247,179 @@ export function createSuspenseBoundary<HostNode, HostElement>(
|
|||||||
fallbackTree: null as any, // will be set immediately after creation
|
fallbackTree: null as any, // will be set immediately after creation
|
||||||
isResolved: false,
|
isResolved: false,
|
||||||
isUnmounted: false,
|
isUnmounted: false,
|
||||||
effects: []
|
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
|
||||||
|
|
||||||
|
// 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 as VNode, parentComponent, suspense, true)
|
||||||
|
}
|
||||||
|
// move content from off-dom container to actual container
|
||||||
|
move(subTree as VNode, container, anchor)
|
||||||
|
const el = (vnode.el = (subTree as VNode).el!)
|
||||||
|
// 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
|
||||||
|
// invoke @resolve event
|
||||||
|
const onResolve = vnode.props && vnode.props.onResolve
|
||||||
|
if (isFunction(onResolve)) {
|
||||||
|
onResolve()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
restart() {
|
||||||
|
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)
|
||||||
|
move(subTree as VNode, hiddenContainer, null)
|
||||||
|
// remount the fallback tree
|
||||||
|
patch(
|
||||||
|
null,
|
||||||
|
fallbackTree,
|
||||||
|
container,
|
||||||
|
anchor,
|
||||||
|
parentComponent,
|
||||||
|
null, // fallback tree will not have suspense context
|
||||||
|
isSVG,
|
||||||
|
optimized
|
||||||
|
)
|
||||||
|
const el = (vnode.el = (fallbackTree as VNode).el!)
|
||||||
|
// suspense as the root node of a component...
|
||||||
|
if (parentComponent && parentComponent.subTree === vnode) {
|
||||||
|
parentComponent.vnode.el = el
|
||||||
|
updateHOCHostEl(parentComponent, el)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invoke @suspense event
|
||||||
|
const onSuspense = vnode.props && vnode.props.onSuspense
|
||||||
|
if (isFunction(onSuspense)) {
|
||||||
|
onSuspense()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
suspense.restart()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
handleSetupResult(instance, asyncSetupResult, suspense)
|
||||||
|
setupRenderEffect(
|
||||||
|
instance,
|
||||||
|
suspense,
|
||||||
|
vnode,
|
||||||
|
// component may have been moved before resolve
|
||||||
|
parentNode(instance.subTree.el)!,
|
||||||
|
next(instance.subTree),
|
||||||
|
isSVG
|
||||||
|
)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeSuspenseChildren(
|
return suspense
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSuspenseChildren(
|
||||||
vnode: VNode
|
vnode: VNode
|
||||||
): {
|
): {
|
||||||
content: VNode
|
content: VNode
|
||||||
|
@ -16,15 +16,18 @@ import { RawSlots } from './componentSlots'
|
|||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { isReactive } from '@vue/reactivity'
|
import { isReactive } from '@vue/reactivity'
|
||||||
import { AppContext } from './apiApp'
|
import { AppContext } from './apiApp'
|
||||||
import { SuspenseBoundary } from './suspense'
|
import { SuspenseBoundary, isSuspenseType } from './suspense'
|
||||||
import { DirectiveBinding } from './directives'
|
import { DirectiveBinding } from './directives'
|
||||||
|
import { SuspenseImpl } from './suspense'
|
||||||
|
|
||||||
export const Fragment = Symbol(__DEV__ ? 'Fragment' : undefined)
|
export const Fragment = Symbol(__DEV__ ? 'Fragment' : undefined)
|
||||||
export const Portal = Symbol(__DEV__ ? 'Portal' : undefined)
|
export const Portal = Symbol(__DEV__ ? 'Portal' : undefined)
|
||||||
export const Suspense = Symbol(__DEV__ ? 'Suspense' : undefined)
|
|
||||||
export const Text = Symbol(__DEV__ ? 'Text' : undefined)
|
export const Text = Symbol(__DEV__ ? 'Text' : undefined)
|
||||||
export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
|
export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
|
||||||
|
|
||||||
|
const Suspense = __FEATURE_SUSPENSE__ ? SuspenseImpl : null
|
||||||
|
export { Suspense }
|
||||||
|
|
||||||
export type VNodeTypes =
|
export type VNodeTypes =
|
||||||
| string
|
| string
|
||||||
| Component
|
| Component
|
||||||
@ -32,7 +35,7 @@ export type VNodeTypes =
|
|||||||
| typeof Portal
|
| typeof Portal
|
||||||
| typeof Text
|
| typeof Text
|
||||||
| typeof Comment
|
| typeof Comment
|
||||||
| typeof Suspense
|
| typeof SuspenseImpl
|
||||||
|
|
||||||
type VNodeChildAtom<HostNode, HostElement> =
|
type VNodeChildAtom<HostNode, HostElement> =
|
||||||
| VNode<HostNode, HostElement>
|
| VNode<HostNode, HostElement>
|
||||||
@ -187,6 +190,8 @@ export function createVNode(
|
|||||||
// encode the vnode type information into a bitmap
|
// encode the vnode type information into a bitmap
|
||||||
const shapeFlag = isString(type)
|
const shapeFlag = isString(type)
|
||||||
? ShapeFlags.ELEMENT
|
? ShapeFlags.ELEMENT
|
||||||
|
: __FEATURE_SUSPENSE__ && isSuspenseType(type)
|
||||||
|
? ShapeFlags.SUSPENSE
|
||||||
: isObject(type)
|
: isObject(type)
|
||||||
? ShapeFlags.STATEFUL_COMPONENT
|
? ShapeFlags.STATEFUL_COMPONENT
|
||||||
: isFunction(type)
|
: isFunction(type)
|
||||||
|
Loading…
Reference in New Issue
Block a user