fix(reactivity): ensure multiple effectScope on() and off() calls maintains correct active scope

close #12631
close #12632

This is a combination of changes from both 8dec243 and #12641
This commit is contained in:
Evan You 2025-01-08 18:07:44 +08:00
parent e8e842241a
commit 22dcbf3e20
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
2 changed files with 41 additions and 2 deletions

View File

@ -8,6 +8,10 @@ export class EffectScope {
* @internal * @internal
*/ */
private _active = true private _active = true
/**
* @internal track `on` calls, allow `on` call multiple times
*/
private _on = 0
/** /**
* @internal * @internal
*/ */
@ -99,20 +103,27 @@ export class EffectScope {
} }
} }
prevScope: EffectScope | undefined
/** /**
* This should only be called on non-detached scopes * This should only be called on non-detached scopes
* @internal * @internal
*/ */
on(): void { on(): void {
if (++this._on === 1) {
this.prevScope = activeEffectScope
activeEffectScope = this activeEffectScope = this
} }
}
/** /**
* This should only be called on non-detached scopes * This should only be called on non-detached scopes
* @internal * @internal
*/ */
off(): void { off(): void {
activeEffectScope = this.parent if (this._on > 0 && --this._on === 0) {
activeEffectScope = this.prevScope
this.prevScope = undefined
}
} }
stop(fromParent?: boolean): void { stop(fromParent?: boolean): void {

View File

@ -31,6 +31,7 @@ import {
TrackOpTypes, TrackOpTypes,
TriggerOpTypes, TriggerOpTypes,
effectScope, effectScope,
onScopeDispose,
shallowReactive, shallowReactive,
shallowRef, shallowRef,
toRef, toRef,
@ -1982,4 +1983,31 @@ describe('api: watch', () => {
expect(spy1).toHaveBeenCalled() expect(spy1).toHaveBeenCalled()
expect(spy2).toHaveBeenCalled() expect(spy2).toHaveBeenCalled()
}) })
// #12631
test('this.$watch w/ onScopeDispose', () => {
const onCleanup = vi.fn()
const toggle = ref(true)
const Comp = defineComponent({
render() {},
created(this: any) {
this.$watch(
() => 1,
function () {},
)
onScopeDispose(onCleanup)
},
})
const App = defineComponent({
render() {
return toggle.value ? h(Comp) : null
},
})
const root = nodeOps.createElement('div')
createApp(App).mount(root)
expect(onCleanup).toBeCalledTimes(0)
})
}) })