What Is effectScope?
effectScope is a low‑level Vue 3 API that groups reactive effects—such as watch, watchEffect, and computed—into a single, controllable scope.
- All effects created inside the scope are tracked together.
- The scope can be stopped with a single call, disposing every contained effect.
- It decouples reactivity from the implicit component instance or global execution context.
Why Use effectScope?
Reactive effects that live longer than intended cause memory leaks, stale updates, and unpredictable performance. effectScope solves these problems by giving developers explicit lifecycle control.
- Prevent runaway watchers that keep reacting after a component is unmounted.
- Isolate reactivity for services, plugins, or background tasks that exist outside a component.
- Facilitate debugging—you can stop a scope to see which effects were still active.
How to Create and Use an effectScope
Basic Syntax
```js import { effectScope } from 'vue' const scope = effectScope() scope.run(() => { // any watch/watchEffect/computed placed here belongs to the scope watchEffect(() => console.log('reactive')) }) // Later, when the effects are no longer needed scope.stop() ```
scope.run(fn)executesfnwhile collecting all reactive effects.scope.stop()disposes every effect created inside the run.
Using Inside a Composable
Composables often need their own cleanup logic. Wrapping their internal reactivity in an effectScope lets the consumer decide when to stop it.
```js export function useExternalService() { const scope = effectScope() const data = ref(null) scope.run(() => { watchEffect(() => { // react to external API changes data.value = fetchFromService() }) }) // expose a manual stop method function stop() { scope.stop() } return { data, stop } } ```
- The composable owns its reactivity.
- Consumers can call
stop()when the service is no longer needed.
EffectScope in Plugins or Services
When building a Vue plugin that runs background tasks (e.g., WebSocket listeners), wrap the listeners in a scope attached to the app instance.
```js export default { install(app) { const wsScope = effectScope() wsScope.run(() => { watchEffect(() => { // react to incoming messages handleMessage(ws.message) }) }) app.config.globalProperties.$stopWs = () => wsScope.stop() } } ```
- All watchers related to the WebSocket are stopped with
$stopWs().
When to Use effectScope
- Complex composables that may be instantiated multiple times.
- Reactivity that lives outside component lifecycles (services, global stores, external APIs).
- Plugins that need a clean shutdown path.
- Debugging memory‑leak issues where watchers persist after unmount.
When Not to Use effectScope
- Simple component‑local watchers—Vue already ties them to the component’s lifecycle.
- Situations where the added abstraction outweighs the benefit.
Best Practices & Pitfalls
- Always call
scope.stop()in aonUnmountedhook or equivalent cleanup step. - Never store a scope globally unless you intend to manage its lifetime manually.
- Prefer the built‑in component lifecycle for most UI‑related effects; reserve
effectScopefor cross‑component or service‑level reactivity. - Combine with
onScopeDisposeif you need to run extra cleanup when a component’s own scope ends.