wip: suspense

This commit is contained in:
Evan You 2019-09-09 13:59:53 -04:00
parent cc748db33b
commit 1dc9d81e3e
3 changed files with 93 additions and 65 deletions

View File

@ -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,

View File

@ -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
}

View File

@ -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