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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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