From b447aceac552fba9d4dcf3adf25313b661e496d1 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Tue, 16 Apr 2024 16:55:44 +0800 Subject: [PATCH] fix(runtime-vapor): detach effect scope & component instance (#174) --- .../runtime-vapor/__tests__/dom/prop.spec.ts | 10 +++++-- .../__tests__/renderEffect.spec.ts | 29 +++++++++++++++++++ packages/runtime-vapor/src/apiLifecycle.ts | 4 ++- packages/runtime-vapor/src/apiRender.ts | 2 ++ packages/runtime-vapor/src/component.ts | 2 -- .../runtime-vapor/src/componentLifecycle.ts | 2 +- packages/runtime-vapor/src/componentProps.ts | 2 +- packages/runtime-vapor/src/renderEffect.ts | 24 +++++++++++---- 8 files changed, 62 insertions(+), 13 deletions(-) diff --git a/packages/runtime-vapor/__tests__/dom/prop.spec.ts b/packages/runtime-vapor/__tests__/dom/prop.spec.ts index 325162a3d..8d21919e3 100644 --- a/packages/runtime-vapor/__tests__/dom/prop.spec.ts +++ b/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 } diff --git a/packages/runtime-vapor/__tests__/renderEffect.spec.ts b/packages/runtime-vapor/__tests__/renderEffect.spec.ts index aa1c91a18..90c431959 100644 --- a/packages/runtime-vapor/__tests__/renderEffect.spec.ts +++ b/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() @@ -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) + }) }) diff --git a/packages/runtime-vapor/src/apiLifecycle.ts b/packages/runtime-vapor/src/apiLifecycle.ts index e0df48ed2..104841586 100644 --- a/packages/runtime-vapor/src/apiLifecycle.ts +++ b/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 diff --git a/packages/runtime-vapor/src/apiRender.ts b/packages/runtime-vapor/src/apiRender.ts index 894fb38cd..3129c8508 100644 --- a/packages/runtime-vapor/src/apiRender.ts +++ b/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) { diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 482be5661..e33233e10 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/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 } } diff --git a/packages/runtime-vapor/src/componentLifecycle.ts b/packages/runtime-vapor/src/componentLifecycle.ts index 2dd94b1d5..272e56592 100644 --- a/packages/runtime-vapor/src/componentLifecycle.ts +++ b/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() diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index 7975afe4e..4745b57bb 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/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 { diff --git a/packages/runtime-vapor/src/renderEffect.ts b/packages/runtime-vapor/src/renderEffect.ts index 8da564af5..01f9dca5f 100644 --- a/packages/runtime-vapor/src/renderEffect.ts +++ b/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