Vue Tip: Use effectScope for Managing and Cleaning Up Reactive Effects
Michael Hoffmann
@mokkapps

Vue provides the effectScope
API to group and cleanup multiple reactive effects.
An EffectScope
instance can automatically collect effects run within a synchronous function so that these effects can be disposed together at a later time.
Let's take a look at a simple example:
// effect, computed, watch, watchEffect created inside the scope will be collected
const scope = effectScope()
scope.run(() => {
const doubled = computed(() => counter.value * 2)
watch(doubled, () => console.log(doubled.value))
watchEffect(() => console.log('Count: ', doubled.value))
})
// Dispose of all effects in the scope
scope.stop()
Real World Example
A practical application is the createSharedComposable
function in VueUse:
import type { EffectScope } from 'vue'
import type { AnyFn } from '../utils'
import { effectScope } from 'vue'
import { tryOnScopeDispose } from '../tryOnScopeDispose'
/**
* Make a composable function usable with multiple Vue instances.
*
* @see https://vueuse.org/createSharedComposable
*/
export function createSharedComposable<Fn extends AnyFn>(composable: Fn): Fn {
let subscribers = 0
let state: ReturnType<Fn> | undefined
let scope: EffectScope | undefined
const dispose = () => {
subscribers -= 1
if (scope && subscribers <= 0) {
scope.stop()
state = undefined
scope = undefined
}
}
return <Fn>((...args) => {
subscribers += 1
if (!scope) {
scope = effectScope(true)
state = scope.run(() => composable(...args))
}
tryOnScopeDispose(dispose)
return state
})
}
This function creates a shared composable that efficiently manages side effects across multiple components. For example, using a useMouse
hook, you can prevent multiple event listeners from being added:
function useMouse() {
const x = ref(0)
const y = ref(0)
function handler(e) {
x.value = e.x
y.value = e.y
}
window.addEventListener('mousemove', handler)
onUnmounted(() => {
window.removeEventListener('mousemove', handler)
})
return { x, y }
}
const useSharedMouse = createSharedComposable(useMouse);
Conclusion
effectScope
allows developers to manage reactive effects more efficiently and clearly. By leveraging this powerful feature, you can ensure your applications are not only more performant but also easier to maintain.
If you liked this Vue tip, follow me on BlueSky to get notified about new tips, blog posts, and more. Alternatively (or additionally), you can subscribe to my weekly Vue & Nuxt newsletter :