wip: suspense ideas

This commit is contained in:
Evan You 2019-09-07 11:28:40 -04:00
parent a45d9567df
commit 8b3aa60a18
3 changed files with 113 additions and 2 deletions

View File

@ -5,12 +5,14 @@ import {
Portal, Portal,
normalizeVNode, normalizeVNode,
VNode, VNode,
VNodeChildren VNodeChildren,
Suspense
} from './vnode' } from './vnode'
import { import {
ComponentInternalInstance, ComponentInternalInstance,
createComponentInstance, createComponentInstance,
setupStatefulComponent setupStatefulComponent,
setCurrentInstance
} from './component' } from './component'
import { import {
renderComponentRoot, renderComponentRoot,
@ -40,6 +42,12 @@ 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 {
SuspenseSymbol,
createSuspenseBoundary,
SuspenseBoundary
} from './suspense'
import { provide } from './apiInject'
const prodEffectOptions = { const prodEffectOptions = {
scheduler: queueJob scheduler: queueJob
@ -187,6 +195,17 @@ export function createRenderer<
optimized optimized
) )
break break
case Suspense:
processSuspense(
n1,
n2,
container,
anchor,
parentComponent,
isSVG,
optimized
)
break
default: default:
if (shapeFlag & ShapeFlags.ELEMENT) { if (shapeFlag & ShapeFlags.ELEMENT) {
processElement( processElement(
@ -575,6 +594,44 @@ export function createRenderer<
processEmptyNode(n1, n2, container, anchor) processEmptyNode(n1, n2, container, anchor)
} }
function processSuspense(
n1: HostVNode | null,
n2: HostVNode,
container: HostElement,
anchor: HostNode | null,
parentComponent: ComponentInternalInstance | null,
isSVG: boolean,
optimized: boolean
) {
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)
// start mounting the subtree off-dom
// - tracking async deps and buffering postQueue jobs on current boundary
// 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.
} else {
const suspense = (n2.suspense = n1.suspense) as SuspenseBoundary
}
}
function processComponent( function processComponent(
n1: HostVNode | null, n1: HostVNode | null,
n2: HostVNode, n2: HostVNode,

View File

@ -0,0 +1,48 @@
import { warn } from './warning'
export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
export interface SuspenseBoundary {
deps: number
isResolved: boolean
parent: SuspenseBoundary | null
ping(): void
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 = {
deps: 0,
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.`)
}
},
resolve() {
boundary.isResolved = true
if (parent && !parent.isResolved) {
parent.ping()
} else {
onResolve && onResolve()
}
},
onResolve(cb: () => void) {
onResolve = cb
}
}
return boundary
}

View File

@ -12,11 +12,13 @@ import { PatchFlags } from './patchFlags'
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'
export const Fragment = __DEV__ ? Symbol('Fragment') : Symbol() export const Fragment = __DEV__ ? Symbol('Fragment') : Symbol()
export const Text = __DEV__ ? Symbol('Text') : Symbol() export const Text = __DEV__ ? Symbol('Text') : Symbol()
export const Empty = __DEV__ ? Symbol('Empty') : Symbol() export const Empty = __DEV__ ? Symbol('Empty') : Symbol()
export const Portal = __DEV__ ? Symbol('Portal') : Symbol() export const Portal = __DEV__ ? Symbol('Portal') : Symbol()
export const Suspense = __DEV__ ? Symbol('Suspense') : Symbol()
export type VNodeTypes = export type VNodeTypes =
| string | string
@ -26,6 +28,7 @@ export type VNodeTypes =
| typeof Portal | typeof Portal
| typeof Text | typeof Text
| typeof Empty | typeof Empty
| typeof Suspense
type VNodeChildAtom<HostNode, HostElement> = type VNodeChildAtom<HostNode, HostElement> =
| VNode<HostNode, HostElement> | VNode<HostNode, HostElement>
@ -58,6 +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
// DOM // DOM
el: HostNode | null el: HostNode | null
@ -168,6 +172,7 @@ export function createVNode(
ref: (props && props.ref) || null, ref: (props && props.ref) || null,
children: null, children: null,
component: null, component: null,
suspense: null,
el: null, el: null,
anchor: null, anchor: null,
target: null, target: null,
@ -221,6 +226,7 @@ export function cloneVNode(vnode: VNode): VNode {
// mounted VNodes. If they are somehow not null, this means we have // mounted VNodes. If they are somehow not null, this means we have
// encountered an already-mounted vnode being used again. // encountered an already-mounted vnode being used again.
component: null, component: null,
suspense: null,
el: null, el: null,
anchor: null anchor: null
} }