]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: watcher callback handling inside suspense
authorEvan You <yyx990803@gmail.com>
Wed, 11 Sep 2019 14:09:00 +0000 (10:09 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 11 Sep 2019 15:10:13 +0000 (11:10 -0400)
packages/runtime-core/__tests__/rendererSuspense.spec.ts
packages/runtime-core/src/apiLifecycle.ts
packages/runtime-core/src/apiWatch.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/createRenderer.ts
packages/runtime-core/src/suspense.ts

index 9b1e41f07191a232f1cfee71dbea6bd9c6134407..16953bee44d9eb0498402535373c35a571397e9a 100644 (file)
@@ -115,6 +115,8 @@ describe('renderer: suspense', () => {
 
   test.todo('buffer mounted/updated hooks & watch callbacks')
 
+  test.todo('onResolve')
+
   test.todo('content update before suspense resolve')
 
   test.todo('unmount before suspense resolve')
index 12f840f88c6fd5a619751108c29268f5be94b389..39782388044d4e41d597f0843c617523c923324c 100644 (file)
@@ -39,7 +39,11 @@ function injectHook(
     warn(
       `${apiName} is called when there is no active component instance to be ` +
         `associated with. ` +
-        `Lifecycle injection APIs can only be used during execution of setup().`
+        `Lifecycle injection APIs can only be used during execution of setup().` +
+        (__FEATURE_SUSPENSE__
+          ? ` If you are using async setup(), make sure to register lifecycle ` +
+            `hooks before the first await statement.`
+          : ``)
     )
   }
 }
index e2ebd0bdc37c787adbb073797640b3250d773aa3..478a0f66cb8ccc6c2f8b3e6fc4d2c0f9d86d2053 100644 (file)
@@ -5,16 +5,21 @@ import {
   Ref,
   ReactiveEffectOptions
 } from '@vue/reactivity'
-import { queueJob, queuePostFlushCb } from './scheduler'
+import { queueJob } from './scheduler'
 import { EMPTY_OBJ, isObject, isArray, isFunction, isString } from '@vue/shared'
 import { recordEffect } from './apiReactivity'
-import { currentInstance, ComponentInternalInstance } from './component'
+import {
+  currentInstance,
+  ComponentInternalInstance,
+  currentSuspense
+} from './component'
 import {
   ErrorCodes,
   callWithErrorHandling,
   callWithAsyncErrorHandling
 } from './errorHandling'
-import { onBeforeMount } from './apiLifecycle'
+import { onBeforeUnmount } from './apiLifecycle'
+import { queuePostRenderEffect } from './createRenderer'
 
 export interface WatchOptions {
   lazy?: boolean
@@ -38,14 +43,17 @@ type SimpleEffect = (onCleanup: CleanupRegistrator) => void
 
 const invoke = (fn: Function) => fn()
 
+// overload #1: simple effect
 export function watch(effect: SimpleEffect, options?: WatchOptions): StopHandle
 
+// overload #2: single source + cb
 export function watch<T>(
   source: WatcherSource<T>,
   cb: (newValue: T, oldValue: T, onCleanup: CleanupRegistrator) => any,
   options?: WatchOptions
 ): StopHandle
 
+// overload #3: array of multiple sources + cb
 export function watch<T extends WatcherSource<unknown>[]>(
   sources: T,
   cb: (
@@ -85,6 +93,7 @@ function doWatch(
   { lazy, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
 ): StopHandle {
   const instance = currentInstance
+  const suspense = currentSuspense
 
   let getter: Function
   if (isArray(source)) {
@@ -152,7 +161,7 @@ function doWatch(
     flush === 'sync'
       ? invoke
       : flush === 'pre'
-        ? (job: () => void) => {
+        ? (job: () => any) => {
             if (!instance || instance.vnode.el != null) {
               queueJob(job)
             } else {
@@ -161,7 +170,7 @@ function doWatch(
               job()
             }
           }
-        : queuePostFlushCb
+        : (job: () => any) => queuePostRenderEffect(job, suspense)
 
   const runner = effect(getter, {
     lazy: true,
@@ -198,7 +207,7 @@ export function instanceWatch(
   const ctx = this.renderProxy as any
   const getter = isString(source) ? () => ctx[source] : source.bind(ctx)
   const stop = watch(getter, cb.bind(ctx), options)
-  onBeforeMount(stop, this)
+  onBeforeUnmount(stop, this)
   return stop
 }
 
index e09cd73d6abb759ad0611617831a9013fb28247c..500ac828a9d0efa8ddfffe9edcbe02874160597d 100644 (file)
@@ -23,6 +23,7 @@ import {
   isArray,
   isObject
 } from '@vue/shared'
+import { SuspenseBoundary } from './suspense'
 
 export type Data = { [key: string]: unknown }
 
@@ -206,6 +207,7 @@ export function createComponentInstance(
 }
 
 export let currentInstance: ComponentInternalInstance | null = null
+export let currentSuspense: SuspenseBoundary | null = null
 
 export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
   currentInstance
@@ -216,7 +218,10 @@ export const setCurrentInstance = (
   currentInstance = instance
 }
 
-export function setupStatefulComponent(instance: ComponentInternalInstance) {
+export function setupStatefulComponent(
+  instance: ComponentInternalInstance,
+  parentSuspense: SuspenseBoundary | null
+) {
   const Component = instance.type as ComponentOptions
   // 1. create render proxy
   instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers) as any
@@ -231,6 +236,7 @@ export function setupStatefulComponent(instance: ComponentInternalInstance) {
       setup.length > 1 ? createSetupContext(instance) : null)
 
     currentInstance = instance
+    currentSuspense = parentSuspense
     const setupResult = callWithErrorHandling(
       setup,
       instance,
@@ -238,6 +244,7 @@ export function setupStatefulComponent(instance: ComponentInternalInstance) {
       [propsProxy, setupContext]
     )
     currentInstance = null
+    currentSuspense = null
 
     if (
       setupResult &&
@@ -256,16 +263,17 @@ export function setupStatefulComponent(instance: ComponentInternalInstance) {
       }
       return
     } else {
-      handleSetupResult(instance, setupResult)
+      handleSetupResult(instance, setupResult, parentSuspense)
     }
   } else {
-    finishComponentSetup(instance)
+    finishComponentSetup(instance, parentSuspense)
   }
 }
 
 export function handleSetupResult(
   instance: ComponentInternalInstance,
-  setupResult: unknown
+  setupResult: unknown,
+  parentSuspense: SuspenseBoundary | null
 ) {
   if (isFunction(setupResult)) {
     // setup returned an inline render function
@@ -281,10 +289,13 @@ export function handleSetupResult(
       }`
     )
   }
-  finishComponentSetup(instance)
+  finishComponentSetup(instance, parentSuspense)
 }
 
-function finishComponentSetup(instance: ComponentInternalInstance) {
+function finishComponentSetup(
+  instance: ComponentInternalInstance,
+  parentSuspense: SuspenseBoundary | null
+) {
   const Component = instance.type as ComponentOptions
   if (!instance.render) {
     if (__DEV__ && !Component.render) {
@@ -299,8 +310,10 @@ function finishComponentSetup(instance: ComponentInternalInstance) {
   // support for 2.x options
   if (__FEATURE_OPTIONS__) {
     currentInstance = instance
+    currentSuspense = parentSuspense
     applyOptions(instance, Component)
     currentInstance = null
+    currentSuspense = null
   }
 
   if (instance.renderContext === EMPTY_OBJ) {
index eea24aa33fadcaec771736136a968438a09c4eb0..302b6838b1c124d82c5c627c73ea85fbe35c55de 100644 (file)
@@ -78,7 +78,7 @@ function invokeHooks(hooks: Function[], arg?: any) {
   }
 }
 
-function queuePostEffect(
+export function queuePostRenderEffect(
   fn: Function | Function[],
   suspense: SuspenseBoundary<any, any> | null
 ) {
@@ -357,7 +357,7 @@ export function createRenderer<
     }
     hostInsert(el, container, anchor)
     if (props != null && props.vnodeMounted != null) {
-      queuePostEffect(() => {
+      queuePostRenderEffect(() => {
         invokeDirectiveHook(props.vnodeMounted, parentComponent, vnode)
       }, parentSuspense)
     }
@@ -508,7 +508,7 @@ export function createRenderer<
     }
 
     if (newProps.vnodeUpdated != null) {
-      queuePostEffect(() => {
+      queuePostRenderEffect(() => {
         invokeDirectiveHook(newProps.vnodeUpdated, parentComponent, n2, n1)
       }, parentSuspense)
     }
@@ -700,7 +700,9 @@ export function createRenderer<
       function resolveSuspense() {
         const { subTree, fallbackTree, effects, vnode } = suspense
         // unmount fallback tree
-        unmount(fallbackTree as HostVNode, parentComponent, suspense, true)
+        if (fallback.el) {
+          unmount(fallbackTree as HostVNode, parentComponent, suspense, true)
+        }
         // move content from off-dom container to actual container
         move(subTree as HostVNode, container, anchor)
         const el = (vnode.el = (subTree as HostVNode).el as HostNode)
@@ -895,7 +897,7 @@ export function createRenderer<
 
     // setup stateful logic
     if (initialVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
-      setupStatefulComponent(instance)
+      setupStatefulComponent(instance, parentSuspense)
     }
 
     // setup() is async. This component relies on async logic to be resolved
@@ -909,7 +911,7 @@ export function createRenderer<
         parentSuspense.deps--
         // retry from this component
         instance.asyncResolved = true
-        handleSetupResult(instance, asyncSetupResult)
+        handleSetupResult(instance, asyncSetupResult, parentSuspense)
         setupRenderEffect(
           instance,
           parentSuspense,
@@ -965,7 +967,7 @@ export function createRenderer<
         initialVNode.el = subTree.el
         // mounted hook
         if (instance.m !== null) {
-          queuePostEffect(instance.m, parentSuspense)
+          queuePostRenderEffect(instance.m, parentSuspense)
         }
         mounted = true
       } else {
@@ -1018,7 +1020,7 @@ export function createRenderer<
         }
         // upated hook
         if (instance.u !== null) {
-          queuePostEffect(instance.u, parentSuspense)
+          queuePostRenderEffect(instance.u, parentSuspense)
         }
 
         if (__DEV__) {
@@ -1500,7 +1502,7 @@ export function createRenderer<
     }
 
     if (props != null && props.vnodeUnmounted != null) {
-      queuePostEffect(() => {
+      queuePostRenderEffect(() => {
         invokeDirectiveHook(props.vnodeUnmounted, parentComponent, vnode)
       }, parentSuspense)
     }
@@ -1525,9 +1527,9 @@ export function createRenderer<
     unmount(subTree, instance, parentSuspense, doRemove)
     // unmounted hook
     if (um !== null) {
-      queuePostEffect(um, parentSuspense)
+      queuePostRenderEffect(um, parentSuspense)
       // set unmounted after unmounted hooks are fired
-      queuePostEffect(() => {
+      queuePostRenderEffect(() => {
         instance.isUnmounted = true
       }, parentSuspense)
     }
index e1d6065f32f522d2d7ada3355a5b6864e5b255cf..b264b8a1a0e18154e2b341039eee3456e547f173 100644 (file)
@@ -5,8 +5,8 @@ import { isFunction } from '@vue/shared'
 export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
 
 export interface SuspenseBoundary<
-  HostNode,
-  HostElement,
+  HostNode = any,
+  HostElement = any,
   HostVNode = VNode<HostNode, HostElement>
 > {
   vnode: HostVNode