Просмотр исходного кода

fix(runtime-vapor): detach effect scope & component instance (#174)

Rizumu Ayaka 2 лет назад
Родитель
Сommit
b447aceac5

+ 7 - 3
packages/runtime-vapor/__tests__/dom/prop.spec.ts

@@ -14,13 +14,17 @@ import {
   setCurrentInstance,
 } from '../../src/component'
 import { getMetadata, recordPropMetadata } from '../../src/componentMetadata'
+import { getCurrentScope } from '@vue/reactivity'
 
 let removeComponentInstance = NOOP
 beforeEach(() => {
-  const reset = setCurrentInstance(
-    createComponentInstance((() => {}) as any, {}),
-  )
+  const instance = createComponentInstance((() => {}) as any, {})
+  const reset = setCurrentInstance(instance)
+  const prev = getCurrentScope()
+  instance.scope.on()
   removeComponentInstance = () => {
+    instance.scope.prevScope = prev
+    instance.scope.off()
     reset()
     removeComponentInstance = NOOP
   }

+ 29 - 0
packages/runtime-vapor/__tests__/renderEffect.spec.ts

@@ -1,4 +1,6 @@
 import {
+  EffectScope,
+  getCurrentScope,
   nextTick,
   onBeforeUpdate,
   onEffectCleanup,
@@ -10,6 +12,10 @@ import {
   watchPostEffect,
   watchSyncEffect,
 } from '../src'
+import {
+  type ComponentInternalInstance,
+  currentInstance,
+} from '../src/component'
 import { makeRender } from './_utils'
 
 const define = makeRender<any>()
@@ -207,4 +213,27 @@ describe('renderEffect', () => {
       '[Vue warn] Unhandled error during execution of updated',
     ).toHaveBeenWarned()
   })
+
+  test('should be called with the current instance and current scope', async () => {
+    const source = ref(0)
+    const scope = new EffectScope()
+    let instanceSnap: ComponentInternalInstance | null = null
+    let scopeSnap: EffectScope | undefined = undefined
+    const { instance } = define(() => {
+      scope.run(() => {
+        renderEffect(() => {
+          instanceSnap = currentInstance
+          scopeSnap = getCurrentScope()
+        })
+      })
+    }).render()
+
+    expect(instanceSnap).toBe(instance)
+    expect(scopeSnap).toBe(scope)
+
+    source.value++
+    await nextTick()
+    expect(instanceSnap).toBe(instance)
+    expect(scopeSnap).toBe(scope)
+  })
 })

+ 3 - 1
packages/runtime-vapor/src/apiLifecycle.ts

@@ -39,7 +39,9 @@ const injectHook = (
         }
         pauseTracking()
         const reset = setCurrentInstance(target)
-        const res = callWithAsyncErrorHandling(hook, target, type, args)
+        const res = target.scope.run(() =>
+          callWithAsyncErrorHandling(hook, target, type, args),
+        )
         reset()
         resetTracking()
         return res

+ 2 - 0
packages/runtime-vapor/src/apiRender.ts

@@ -64,7 +64,9 @@ export function setupComponent(
       instance.setupState = proxyRefs(stateOrNode)
     }
     if (!block && component.render) {
+      pauseTracking()
       block = component.render(instance.setupState)
+      resetTracking()
     }
 
     if (block instanceof DocumentFragment) {

+ 0 - 2
packages/runtime-vapor/src/component.ts

@@ -182,9 +182,7 @@ export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
 export const setCurrentInstance = (instance: ComponentInternalInstance) => {
   const prev = currentInstance
   currentInstance = instance
-  instance.scope.on()
   return () => {
-    instance.scope.off()
     currentInstance = prev
   }
 }

+ 1 - 1
packages/runtime-vapor/src/componentLifecycle.ts

@@ -17,7 +17,7 @@ export function invokeLifecycle(
     if (hooks) {
       const fn = () => {
         const reset = setCurrentInstance(instance)
-        invokeArrayFns(hooks)
+        instance.scope.run(() => invokeArrayFns(hooks))
         reset()
       }
       post ? queuePostRenderEffect(fn) : fn()

+ 1 - 1
packages/runtime-vapor/src/componentProps.ts

@@ -212,7 +212,7 @@ function resolvePropValue(
         //   value = propsDefaults[key]
         // } else {
         const reset = setCurrentInstance(instance)
-        value = defaultValue.call(null, props)
+        instance.scope.run(() => (value = defaultValue.call(null, props)))
         reset()
         // }
       } else {

+ 19 - 5
packages/runtime-vapor/src/renderEffect.ts

@@ -3,6 +3,7 @@ import {
   ReactiveEffect,
   type SchedulerJob,
   SchedulerJobFlags,
+  getCurrentScope,
 } from '@vue/reactivity'
 import { invokeArrayFns } from '@vue/shared'
 import {
@@ -16,6 +17,7 @@ import { invokeDirectiveHook } from './directives'
 
 export function renderEffect(cb: () => void) {
   const instance = getCurrentInstance()
+  const scope = getCurrentScope()
 
   let effect: ReactiveEffect
 
@@ -53,11 +55,23 @@ export function renderEffect(cb: () => void) {
     }
   }
 
-  effect = new ReactiveEffect(() => {
-    const reset = instance && setCurrentInstance(instance)
-    callWithAsyncErrorHandling(cb, instance, VaporErrorCodes.RENDER_FUNCTION)
-    reset?.()
-  })
+  if (scope) {
+    const baseCb = cb
+    cb = () => scope.run(baseCb)
+  }
+
+  if (instance) {
+    const baseCb = cb
+    cb = () => {
+      const reset = setCurrentInstance(instance)
+      baseCb()
+      reset()
+    }
+  }
+
+  effect = new ReactiveEffect(() =>
+    callWithAsyncErrorHandling(cb, instance, VaporErrorCodes.RENDER_FUNCTION),
+  )
 
   effect.scheduler = () => {
     if (instance) job.id = instance.uid