wip: suspense
This commit is contained in:
parent
cc748db33b
commit
1dc9d81e3e
@ -6,13 +6,13 @@ import {
|
||||
normalizeVNode,
|
||||
VNode,
|
||||
VNodeChildren,
|
||||
Suspense
|
||||
Suspense,
|
||||
createVNode
|
||||
} from './vnode'
|
||||
import {
|
||||
ComponentInternalInstance,
|
||||
createComponentInstance,
|
||||
setupStatefulComponent,
|
||||
setCurrentInstance
|
||||
setupStatefulComponent
|
||||
} from './component'
|
||||
import {
|
||||
renderComponentRoot,
|
||||
@ -42,12 +42,7 @@ import { pushWarningContext, popWarningContext, warn } from './warning'
|
||||
import { invokeDirectiveHook } from './directives'
|
||||
import { ComponentPublicInstance } from './componentPublicInstanceProxy'
|
||||
import { App, createAppAPI } from './apiApp'
|
||||
import {
|
||||
SuspenseSymbol,
|
||||
createSuspenseBoundary,
|
||||
SuspenseBoundary
|
||||
} from './suspense'
|
||||
import { provide } from './apiInject'
|
||||
import { SuspenseBoundary, createSuspenseBoundary } from './suspense'
|
||||
|
||||
const prodEffectOptions = {
|
||||
scheduler: queueJob
|
||||
@ -603,37 +598,70 @@ export function createRenderer<
|
||||
anchor: HostNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
isSVG: boolean,
|
||||
optimized: boolean
|
||||
optimized: boolean,
|
||||
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null = null
|
||||
) {
|
||||
if (n1 == null) {
|
||||
const parentSuspense =
|
||||
parentComponent &&
|
||||
(parentComponent.provides[SuspenseSymbol as any] as SuspenseBoundary)
|
||||
const suspense = (n2.suspense = createSuspenseBoundary(parentSuspense))
|
||||
|
||||
// provide this as the parent suspense for descendents
|
||||
setCurrentInstance(parentComponent)
|
||||
provide(SuspenseSymbol, suspense)
|
||||
setCurrentInstance(null)
|
||||
const contentContainer = hostCreateElement('div')
|
||||
const suspense = (n2.suspense = createSuspenseBoundary(
|
||||
parentSuspense,
|
||||
contentContainer
|
||||
))
|
||||
|
||||
// start mounting the subtree off-dom
|
||||
// - tracking async deps and buffering postQueue jobs on current boundary
|
||||
|
||||
// - TODO tracking async deps and buffering postQueue jobs on current boundary
|
||||
const contentTree = (suspense.contentTree = childrenToFragment(n2))
|
||||
processFragment(
|
||||
null,
|
||||
contentTree as VNode<HostNode, HostElement>,
|
||||
contentContainer,
|
||||
null,
|
||||
parentComponent,
|
||||
isSVG,
|
||||
optimized
|
||||
)
|
||||
// now check if we have encountered any async deps
|
||||
// yes: mount the fallback tree.
|
||||
// Each time an async dep resolves, it pings the boundary
|
||||
// and causes a re-entry.
|
||||
|
||||
// no: just mount the tree
|
||||
// - if have parent boundary that is still not resolved:
|
||||
// merge the buffered jobs into parent
|
||||
// - else: flush buffered jobs.
|
||||
// - mark resolved.
|
||||
if (suspense.deps > 0) {
|
||||
// yes: mount the fallback tree.
|
||||
// Each time an async dep resolves, it pings the boundary
|
||||
// and causes a re-entry.
|
||||
} else {
|
||||
suspense.resolve()
|
||||
}
|
||||
} else {
|
||||
const suspense = (n2.suspense = n1.suspense) as SuspenseBoundary
|
||||
const suspense = (n2.suspense = n1.suspense) as SuspenseBoundary<
|
||||
HostNode,
|
||||
HostElement
|
||||
>
|
||||
const oldContentTree = suspense.contentTree
|
||||
const newContentTree = (suspense.contentTree = childrenToFragment(n2))
|
||||
// patch suspense subTree as fragment
|
||||
processFragment(
|
||||
oldContentTree,
|
||||
newContentTree,
|
||||
container,
|
||||
anchor,
|
||||
parentComponent,
|
||||
isSVG,
|
||||
optimized
|
||||
)
|
||||
if (suspense.deps > 0) {
|
||||
// still pending.
|
||||
// patch the fallback tree.
|
||||
} else {
|
||||
suspense.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function childrenToFragment(vnode: HostVNode): HostVNode {
|
||||
return vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN
|
||||
? createVNode(Fragment, null, vnode.children)
|
||||
: vnode.shapeFlag & ShapeFlags.TEXT_CHILDREN
|
||||
? createVNode(Fragment, null, [vnode.children])
|
||||
: createVNode(Fragment, null, [])
|
||||
}
|
||||
|
||||
function processComponent(
|
||||
n1: HostVNode | null,
|
||||
n2: HostVNode,
|
||||
|
@ -1,48 +1,48 @@
|
||||
import { warn } from './warning'
|
||||
import { VNode } from './vnode'
|
||||
import { queuePostFlushCb } from './scheduler'
|
||||
|
||||
export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
|
||||
|
||||
export interface SuspenseBoundary {
|
||||
export interface SuspenseBoundary<HostNode, HostElement> {
|
||||
parent: SuspenseBoundary<HostNode, HostElement> | null
|
||||
contentTree: VNode<HostNode, HostElement> | null
|
||||
fallbackTree: VNode<HostNode, HostElement> | null
|
||||
deps: number
|
||||
isResolved: boolean
|
||||
parent: SuspenseBoundary | null
|
||||
ping(): void
|
||||
bufferedJobs: Function[]
|
||||
container: HostElement
|
||||
resolve(): void
|
||||
onResolve(cb: () => void): void
|
||||
}
|
||||
|
||||
export function createSuspenseBoundary(
|
||||
parent: SuspenseBoundary | null
|
||||
): SuspenseBoundary {
|
||||
let onResolve: () => void
|
||||
|
||||
if (parent && !parent.isResolved) {
|
||||
parent.deps++
|
||||
}
|
||||
|
||||
const boundary: SuspenseBoundary = {
|
||||
export function createSuspenseBoundary<HostNode, HostElement>(
|
||||
parent: SuspenseBoundary<HostNode, HostElement> | null,
|
||||
container: HostElement
|
||||
): SuspenseBoundary<HostNode, HostElement> {
|
||||
const suspense: SuspenseBoundary<HostNode, HostElement> = {
|
||||
parent,
|
||||
container,
|
||||
deps: 0,
|
||||
contentTree: null,
|
||||
fallbackTree: null,
|
||||
isResolved: false,
|
||||
parent: parent && parent.isResolved ? parent : null,
|
||||
ping() {
|
||||
// one of the deps resolved - re-entry from root suspense
|
||||
if (boundary.parent) {
|
||||
}
|
||||
if (__DEV__ && boundary.deps < 0) {
|
||||
warn(`Suspense boundary pinged when deps === 0. This is a bug.`)
|
||||
}
|
||||
},
|
||||
bufferedJobs: [],
|
||||
resolve() {
|
||||
boundary.isResolved = true
|
||||
if (parent && !parent.isResolved) {
|
||||
parent.ping()
|
||||
} else {
|
||||
onResolve && onResolve()
|
||||
suspense.isResolved = true
|
||||
let parent = suspense.parent
|
||||
let hasUnresolvedAncestor = false
|
||||
while (parent) {
|
||||
if (!parent.isResolved) {
|
||||
parent.bufferedJobs.push(...suspense.bufferedJobs)
|
||||
hasUnresolvedAncestor = true
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
onResolve(cb: () => void) {
|
||||
onResolve = cb
|
||||
if (!hasUnresolvedAncestor) {
|
||||
queuePostFlushCb(suspense.bufferedJobs)
|
||||
}
|
||||
suspense.isResolved = true
|
||||
}
|
||||
}
|
||||
return boundary
|
||||
|
||||
return suspense
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ export interface VNode<HostNode = any, HostElement = any> {
|
||||
ref: string | Function | null
|
||||
children: NormalizedChildren<HostNode, HostElement>
|
||||
component: ComponentInternalInstance | null
|
||||
suspense: SuspenseBoundary | null
|
||||
suspense: SuspenseBoundary<HostNode, HostElement> | null
|
||||
|
||||
// DOM
|
||||
el: HostNode | null
|
||||
|
Loading…
Reference in New Issue
Block a user