Skip to content

Commit da21873

Browse files
committed
fix(effectScope): should have a vaild scope with component
1 parent c34f9ea commit da21873

File tree

5 files changed

+82
-25
lines changed

5 files changed

+82
-25
lines changed

src/apis/effectScope.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
ComponentInternalInstance,
23
getCurrentInstance,
34
getVueConstructor,
45
withCurrentInstanceTrackingDisabled,
@@ -9,7 +10,7 @@ import { warn } from './warn'
910
let activeEffectScope: EffectScope | undefined
1011
const effectScopeStack: EffectScope[] = []
1112

12-
export class EffectScope {
13+
class EffectScopeImpl {
1314
active = true
1415
effects: EffectScope[] = []
1516
cleanups: (() => void)[] = []
@@ -19,15 +20,8 @@ export class EffectScope {
1920
**/
2021
vm: Vue
2122

22-
constructor(detached = false) {
23-
let vm: Vue = undefined!
24-
withCurrentInstanceTrackingDisabled(() => {
25-
vm = defineComponentInstance(getVueConstructor())
26-
})
23+
constructor(vm: Vue) {
2724
this.vm = vm
28-
if (!detached) {
29-
recordEffectScope(this)
30-
}
3125
}
3226

3327
run<T>(fn: () => T): T | undefined {
@@ -68,6 +62,19 @@ export class EffectScope {
6862
}
6963
}
7064

65+
export class EffectScope extends EffectScopeImpl {
66+
constructor(detached = false) {
67+
let vm: Vue = undefined!
68+
withCurrentInstanceTrackingDisabled(() => {
69+
vm = defineComponentInstance(getVueConstructor())
70+
})
71+
super(vm)
72+
if (!detached) {
73+
recordEffectScope(this)
74+
}
75+
}
76+
}
77+
7178
export function recordEffectScope(
7279
effect: EffectScope,
7380
scope?: EffectScope | null
@@ -107,3 +114,17 @@ export function onScopeDispose(fn: () => void) {
107114
export function getCurrentScopeVM() {
108115
return getCurrentScope()?.vm || getCurrentInstance()?.proxy
109116
}
117+
118+
/**
119+
* @internal
120+
**/
121+
export function bindCurrentScopeToVM(
122+
vm: ComponentInternalInstance
123+
): EffectScope {
124+
if (!vm.scope) {
125+
const scope = new EffectScopeImpl(vm.proxy) as EffectScope
126+
vm.scope = scope
127+
vm.proxy.$on('hook:destroyed', () => scope.stop())
128+
}
129+
return vm.scope
130+
}

src/apis/lifecycle.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
setCurrentInstance,
66
getCurrentInstance,
77
ComponentInternalInstance,
8+
setCurrentVue2Instance,
89
} from '../runtimeContext'
910
import { currentVMInFn } from '../utils/helper'
1011

@@ -33,12 +34,12 @@ function injectHookOption(
3334

3435
function wrapHookCall(vm: ComponentInstance, fn: Function): Function {
3536
return (...args: any) => {
36-
let preVm = getCurrentInstance()?.proxy
37-
setCurrentInstance(vm)
37+
let preVm = getCurrentInstance()
38+
setCurrentVue2Instance(vm)
3839
try {
3940
return fn(...args)
4041
} finally {
41-
setCurrentInstance(preVm!)
42+
setCurrentInstance(preVm)
4243
}
4344
}
4445
}

src/runtimeContext.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { VueConstructor, VNode } from 'vue'
2+
import { bindCurrentScopeToVM, EffectScope } from './apis/effectScope'
23
import { ComponentInstance, Data } from './component'
34
import {
45
assert,
@@ -27,7 +28,7 @@ try {
2728
}
2829

2930
let vueConstructor: VueConstructor | null = null
30-
let currentInstance: ComponentInstance | null = null
31+
let currentInstance: ComponentInternalInstance | null = null
3132
let currentInstanceTracking = true
3233

3334
const PluginInstalledFlag = '__composition_api_installed__'
@@ -93,10 +94,17 @@ export function withCurrentInstanceTrackingDisabled(fn: () => void) {
9394
}
9495
}
9596

96-
export function setCurrentInstance(vm: ComponentInstance | null) {
97+
export function setCurrentVue2Instance(vm: ComponentInstance | null) {
9798
if (!currentInstanceTracking) return
98-
// currentInstance?.$scopedSlots
99+
setCurrentInstance(vm ? toVue3ComponentInstance(vm) : vm)
100+
}
101+
102+
export function setCurrentInstance(vm: ComponentInternalInstance | null) {
103+
if (!currentInstanceTracking) return
104+
const prev = currentInstance
105+
prev?.scope.off()
99106
currentInstance = vm
107+
currentInstance?.scope.on()
100108
}
101109

102110
export type Slot = (...args: any[]) => VNode[]
@@ -166,17 +174,15 @@ export declare interface ComponentInternalInstance {
166174
isMounted: boolean
167175
isUnmounted: boolean
168176
isDeactivated: boolean
169-
}
170177

171-
export function getCurrentVue2Instance() {
172-
return currentInstance
178+
/**
179+
* @internal
180+
*/
181+
scope: EffectScope
173182
}
174183

175184
export function getCurrentInstance() {
176-
if (currentInstance) {
177-
return toVue3ComponentInstance(currentInstance)
178-
}
179-
return null
185+
return currentInstance
180186
}
181187

182188
const instanceMapCache = new WeakMap<
@@ -203,6 +209,8 @@ function toVue3ComponentInstance(
203209
root: null!, // to be immediately set
204210
} as unknown as ComponentInternalInstance
205211

212+
bindCurrentScopeToVM(instance)
213+
206214
// map vm.$props =
207215
const instanceProps = [
208216
'data',

src/utils/instance.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { ComponentInstance } from '../component'
22
import vmStateManager from './vmStateManager'
3-
import { setCurrentInstance, getCurrentVue2Instance } from '../runtimeContext'
3+
import {
4+
setCurrentInstance,
5+
getCurrentInstance,
6+
setCurrentVue2Instance,
7+
} from '../runtimeContext'
48
import { Ref, isRef, isReactive } from '../apis'
59
import { hasOwn, proxy, warn } from './utils'
610
import { createSlotProxy, resolveSlots } from './helper'
@@ -129,8 +133,8 @@ export function activateCurrentInstance(
129133
fn: (vm_: ComponentInstance) => any,
130134
onError?: (err: Error) => void
131135
) {
132-
let preVm = getCurrentVue2Instance()
133-
setCurrentInstance(vm)
136+
let preVm = getCurrentInstance()
137+
setCurrentVue2Instance(vm)
134138
try {
135139
return fn(vm)
136140
} catch (err) {

test/v3/reactivity/effectScope.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ref,
1010
ComputedRef,
1111
createApp,
12+
getCurrentScope,
1213
} from '../../../src'
1314
import { mockWarn } from '../../helpers'
1415

@@ -282,4 +283,26 @@ describe('reactivity/effect/scope', () => {
282283
expect(dummy).toBe(7)
283284
expect(doubled).toBe(14)
284285
})
286+
287+
it('component should be a valid scope', async () => {
288+
let dummy = 0
289+
let scope
290+
291+
const root = document.createElement('div')
292+
const vm = createApp({
293+
setup() {
294+
scope = getCurrentScope()
295+
onScopeDispose(() => (dummy += 1))
296+
scope?.cleanups.push(() => (dummy += 1))
297+
},
298+
})
299+
300+
vm.mount(root)
301+
expect(dummy).toBe(0)
302+
expect(scope).not.toBeFalsy()
303+
304+
vm.unmount()
305+
await nextTick()
306+
expect(dummy).toBe(2)
307+
})
285308
})

0 commit comments

Comments
 (0)