Skip to content

Commit fcadec2

Browse files
authored
feat: implement effectScope api (#762)
1 parent 16e831b commit fcadec2

File tree

6 files changed

+399
-5
lines changed

6 files changed

+399
-5
lines changed

src/apis/computed.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { getVueConstructor, getCurrentInstance } from '../runtimeContext'
1+
import { getVueConstructor } from '../runtimeContext'
22
import { createRef, Ref } from '../reactivity'
33
import {
44
warn,
55
noopFn,
66
defineComponentInstance,
77
getVueInternalClasses,
88
} from '../utils'
9+
import { getCurrentScopeVM } from './effectScope'
910

1011
export interface ComputedRef<T = any> extends WritableComputedRef<T> {
1112
readonly value: T
@@ -31,7 +32,7 @@ export function computed<T>(
3132
export function computed<T>(
3233
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
3334
): ComputedRef<T> | WritableComputedRef<T> {
34-
const vm = getCurrentInstance()?.proxy
35+
const vm = getCurrentScopeVM()
3536
let getter: ComputedGetter<T>
3637
let setter: ComputedSetter<T> | undefined
3738

src/apis/effectScope.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {
2+
getCurrentInstance,
3+
getVueConstructor,
4+
withCurrentInstanceTrackingDisabled,
5+
} from '../runtimeContext'
6+
import { defineComponentInstance } from '../utils'
7+
import { warn } from './warn'
8+
9+
let activeEffectScope: EffectScope | undefined
10+
const effectScopeStack: EffectScope[] = []
11+
12+
export class EffectScope {
13+
active = true
14+
effects: EffectScope[] = []
15+
cleanups: (() => void)[] = []
16+
17+
/**
18+
* @internal
19+
**/
20+
vm: Vue
21+
22+
constructor(detached = false) {
23+
let vm: Vue = undefined!
24+
withCurrentInstanceTrackingDisabled(() => {
25+
vm = defineComponentInstance(getVueConstructor())
26+
})
27+
this.vm = vm
28+
if (!detached) {
29+
recordEffectScope(this)
30+
}
31+
}
32+
33+
run<T>(fn: () => T): T | undefined {
34+
if (this.active) {
35+
try {
36+
this.on()
37+
return fn()
38+
} finally {
39+
this.off()
40+
}
41+
} else if (__DEV__) {
42+
warn(`cannot run an inactive effect scope.`)
43+
}
44+
return
45+
}
46+
47+
on() {
48+
if (this.active) {
49+
effectScopeStack.push(this)
50+
activeEffectScope = this
51+
}
52+
}
53+
54+
off() {
55+
if (this.active) {
56+
effectScopeStack.pop()
57+
activeEffectScope = effectScopeStack[effectScopeStack.length - 1]
58+
}
59+
}
60+
61+
stop() {
62+
if (this.active) {
63+
this.vm.$destroy()
64+
this.effects.forEach((e) => e.stop())
65+
this.cleanups.forEach((cleanup) => cleanup())
66+
this.active = false
67+
}
68+
}
69+
}
70+
71+
export function recordEffectScope(
72+
effect: EffectScope,
73+
scope?: EffectScope | null
74+
) {
75+
scope = scope || activeEffectScope
76+
if (scope && scope.active) {
77+
scope.effects.push(effect)
78+
}
79+
}
80+
81+
export function effectScope(detached?: boolean) {
82+
return new EffectScope(detached)
83+
}
84+
85+
export function getCurrentScope() {
86+
return activeEffectScope
87+
}
88+
89+
export function onScopeDispose(fn: () => void) {
90+
if (activeEffectScope) {
91+
activeEffectScope.cleanups.push(fn)
92+
} else if (__DEV__) {
93+
warn(
94+
`onDispose() is called when there is no active effect scope ` +
95+
` to be associated with.`
96+
)
97+
}
98+
}
99+
100+
/**
101+
* @internal
102+
**/
103+
export function getCurrentScopeVM() {
104+
return getCurrentScope()?.vm || getCurrentInstance()?.proxy
105+
}

src/apis/index.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
export * from '../reactivity'
2-
export * from './lifecycle'
2+
export {
3+
onBeforeMount,
4+
onMounted,
5+
onBeforeUpdate,
6+
onUpdated,
7+
onBeforeUnmount,
8+
onUnmounted,
9+
onErrorCaptured,
10+
onActivated,
11+
onDeactivated,
12+
onServerPrefetch,
13+
} from './lifecycle'
314
export * from './watch'
415
export * from './computed'
516
export * from './inject'
@@ -8,3 +19,9 @@ export { App, createApp } from './createApp'
819
export { nextTick } from './nextTick'
920
export { createElement as h } from './createElement'
1021
export { warn } from './warn'
22+
export {
23+
effectScope,
24+
EffectScope,
25+
getCurrentScope,
26+
onScopeDispose,
27+
} from './effectScope'

src/apis/watch.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import {
1313
isMap,
1414
} from '../utils'
1515
import { defineComponentInstance } from '../utils/helper'
16-
import { getCurrentInstance, getVueConstructor } from '../runtimeContext'
16+
import { getVueConstructor } from '../runtimeContext'
1717
import {
1818
WatcherPreFlushQueueKey,
1919
WatcherPostFlushQueueKey,
2020
} from '../utils/symbols'
2121
import { ComputedRef } from './computed'
22+
import { getCurrentScopeVM } from './effectScope'
2223

2324
export type WatchEffect = (onInvalidate: InvalidateCbRegistrator) => void
2425

@@ -110,7 +111,7 @@ function getWatchEffectOption(options?: Partial<WatchOptions>): WatchOptions {
110111
}
111112

112113
function getWatcherVM() {
113-
let vm = getCurrentInstance()?.proxy
114+
let vm = getCurrentScopeVM()
114115
if (!vm) {
115116
if (!fallbackVM) {
116117
fallbackVM = defineComponentInstance(getVueConstructor())

src/runtimeContext.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ try {
2121

2222
let vueConstructor: VueConstructor | null = null
2323
let currentInstance: ComponentInstance | null = null
24+
let currentInstanceTracking = true
2425

2526
const PluginInstalledFlag = '__composition_api_installed__'
2627

@@ -71,7 +72,22 @@ export function setVueConstructor(Vue: VueConstructor) {
7172
})
7273
}
7374

75+
/**
76+
* For `effectScope` to create instance without populate the current instance
77+
* @internal
78+
**/
79+
export function withCurrentInstanceTrackingDisabled(fn: () => void) {
80+
const prev = currentInstanceTracking
81+
currentInstanceTracking = false
82+
try {
83+
fn()
84+
} finally {
85+
currentInstanceTracking = prev
86+
}
87+
}
88+
7489
export function setCurrentInstance(vm: ComponentInstance | null) {
90+
if (!currentInstanceTracking) return
7591
// currentInstance?.$scopedSlots
7692
currentInstance = vm
7793
}

0 commit comments

Comments
 (0)