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

This commit is contained in:
Rizumu Ayaka 2024-04-16 16:55:44 +08:00 committed by GitHub
parent e640ec6088
commit b447aceac5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 62 additions and 13 deletions

View File

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

View File

@ -1,4 +1,6 @@
import { import {
EffectScope,
getCurrentScope,
nextTick, nextTick,
onBeforeUpdate, onBeforeUpdate,
onEffectCleanup, onEffectCleanup,
@ -10,6 +12,10 @@ import {
watchPostEffect, watchPostEffect,
watchSyncEffect, watchSyncEffect,
} from '../src' } from '../src'
import {
type ComponentInternalInstance,
currentInstance,
} from '../src/component'
import { makeRender } from './_utils' import { makeRender } from './_utils'
const define = makeRender<any>() const define = makeRender<any>()
@ -207,4 +213,27 @@ describe('renderEffect', () => {
'[Vue warn] Unhandled error during execution of updated', '[Vue warn] Unhandled error during execution of updated',
).toHaveBeenWarned() ).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)
})
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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