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, normalizeVNode,
VNode, VNode,
VNodeChildren, VNodeChildren,
Suspense Suspense,
createVNode
} from './vnode' } from './vnode'
import { import {
ComponentInternalInstance, ComponentInternalInstance,
createComponentInstance, createComponentInstance,
setupStatefulComponent, setupStatefulComponent
setCurrentInstance
} from './component' } from './component'
import { import {
renderComponentRoot, renderComponentRoot,
@ -42,12 +42,7 @@ import { pushWarningContext, popWarningContext, warn } from './warning'
import { invokeDirectiveHook } from './directives' import { invokeDirectiveHook } from './directives'
import { ComponentPublicInstance } from './componentPublicInstanceProxy' import { ComponentPublicInstance } from './componentPublicInstanceProxy'
import { App, createAppAPI } from './apiApp' import { App, createAppAPI } from './apiApp'
import { import { SuspenseBoundary, createSuspenseBoundary } from './suspense'
SuspenseSymbol,
createSuspenseBoundary,
SuspenseBoundary
} from './suspense'
import { provide } from './apiInject'
const prodEffectOptions = { const prodEffectOptions = {
scheduler: queueJob scheduler: queueJob
@ -603,37 +598,70 @@ export function createRenderer<
anchor: HostNode | null, anchor: HostNode | null,
parentComponent: ComponentInternalInstance | null, parentComponent: ComponentInternalInstance | null,
isSVG: boolean, isSVG: boolean,
optimized: boolean optimized: boolean,
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null = null
) { ) {
if (n1 == null) { if (n1 == null) {
const parentSuspense = const contentContainer = hostCreateElement('div')
parentComponent && const suspense = (n2.suspense = createSuspenseBoundary(
(parentComponent.provides[SuspenseSymbol as any] as SuspenseBoundary) parentSuspense,
const suspense = (n2.suspense = createSuspenseBoundary(parentSuspense)) contentContainer
))
// provide this as the parent suspense for descendents
setCurrentInstance(parentComponent)
provide(SuspenseSymbol, suspense)
setCurrentInstance(null)
// start mounting the subtree off-dom // 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 // now check if we have encountered any async deps
// yes: mount the fallback tree. if (suspense.deps > 0) {
// Each time an async dep resolves, it pings the boundary // yes: mount the fallback tree.
// and causes a re-entry. // Each time an async dep resolves, it pings the boundary
// and causes a re-entry.
// no: just mount the tree } else {
// - if have parent boundary that is still not resolved: suspense.resolve()
// merge the buffered jobs into parent }
// - else: flush buffered jobs.
// - mark resolved.
} else { } 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( function processComponent(
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, 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 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 deps: number
isResolved: boolean isResolved: boolean
parent: SuspenseBoundary | null bufferedJobs: Function[]
ping(): void container: HostElement
resolve(): void resolve(): void
onResolve(cb: () => void): void
} }
export function createSuspenseBoundary( export function createSuspenseBoundary<HostNode, HostElement>(
parent: SuspenseBoundary | null parent: SuspenseBoundary<HostNode, HostElement> | null,
): SuspenseBoundary { container: HostElement
let onResolve: () => void ): SuspenseBoundary<HostNode, HostElement> {
const suspense: SuspenseBoundary<HostNode, HostElement> = {
if (parent && !parent.isResolved) { parent,
parent.deps++ container,
}
const boundary: SuspenseBoundary = {
deps: 0, deps: 0,
contentTree: null,
fallbackTree: null,
isResolved: false, isResolved: false,
parent: parent && parent.isResolved ? parent : null, bufferedJobs: [],
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.`)
}
},
resolve() { resolve() {
boundary.isResolved = true suspense.isResolved = true
if (parent && !parent.isResolved) { let parent = suspense.parent
parent.ping() let hasUnresolvedAncestor = false
} else { while (parent) {
onResolve && onResolve() if (!parent.isResolved) {
parent.bufferedJobs.push(...suspense.bufferedJobs)
hasUnresolvedAncestor = true
break
}
} }
}, if (!hasUnresolvedAncestor) {
onResolve(cb: () => void) { queuePostFlushCb(suspense.bufferedJobs)
onResolve = cb }
suspense.isResolved = true
} }
} }
return boundary
return suspense
} }

View File

@ -61,7 +61,7 @@ export interface VNode<HostNode = any, HostElement = any> {
ref: string | Function | null ref: string | Function | null
children: NormalizedChildren<HostNode, HostElement> children: NormalizedChildren<HostNode, HostElement>
component: ComponentInternalInstance | null component: ComponentInternalInstance | null
suspense: SuspenseBoundary | null suspense: SuspenseBoundary<HostNode, HostElement> | null
// DOM // DOM
el: HostNode | null el: HostNode | null