Evan You 6 rokov pred
rodič
commit
1dc9d81e3e

+ 59 - 31
packages/runtime-core/src/createRenderer.ts

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

+ 33 - 33
packages/runtime-core/src/suspense.ts

@@ -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
+        }
+      }
+      if (!hasUnresolvedAncestor) {
+        queuePostFlushCb(suspense.bufferedJobs)
       }
-    },
-    onResolve(cb: () => void) {
-      onResolve = cb
+      suspense.isResolved = true
     }
   }
-  return boundary
+
+  return suspense
 }

+ 1 - 1
packages/runtime-core/src/vnode.ts

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