Understanding VueUse's createGlobalState and effectScope: A Comparison with Simple Closure Implementation
This article explains how VueUse's createGlobalState leverages Vue's effectScope to provide global state sharing across components, compares it with a straightforward closure approach, and discusses the practical implications of using effectScope for dependency collection and cleanup in frontend development.
Background
After joining a new company, I discovered a clever way to display modal dialogs using a custom hook that encapsulates the visibility flag, props from the parent component, and the modal component itself. The hook exposes an onToggleComponent method, allowing a shared component to render any modal dynamically while binding the appropriate state and events.
The author points out that when many components need to share state in a Vue project, several common approaches exist:
props / emit (parent‑child communication)
provide / inject (multiple components)
state management libraries such as Vuex or Pinia
publish‑subscribe patterns
…
In the specific case of many sibling modal components where only one is visible at a time, the colleague used VueUse's createGlobalState hook to hold the shared state.
createGlobalState Usage
For detailed documentation see VueUse documentation . The basic implementation is shown below:
import { createGlobalState } from '@vueuse/core'
// store.js
import { computed, ref } from 'vue'
export const useGlobalState = createGlobalState(() => {
// state
const count = ref(0)
// getters
const doubleCount = computed(() => count.value * 2)
// actions
function increment() {
count.value++
}
return { count, doubleCount, increment }
})Other components can now import useGlobalState to access count , doubleCount , and increment , achieving convenient state sharing.
effectScope Introduction and Usage
Vue's effectScope creates a scoped reactive effect container with run and stop methods. It collects dependencies inside the scope and can stop tracking them when the scope is stopped.
// Type definition
function effectScope(detached?: boolean): EffectScope
interface EffectScope {
run
(fn: () => T): T | undefined // returns undefined if scope is inactive
stop(): void
} // Example
const scope = effectScope()
scope.run(() => {
const doubled = computed(() => counter.value * 2)
watch(doubled, () => console.log(doubled.value))
watchEffect(() => console.log('Count:', doubled.value))
})
// Clean up all effects in the current scope
scope.stop()Official documentation: Vue EffectScope .
Thoughts
The effectScope API provides a way to manually collect and stop tracking dependencies. VueUse uses it inside createGlobalState , but the implementation never calls stop , so the scope is never deactivated. This raises the question: if stopping is unnecessary, why not implement the hook with a simple closure?
createGlobalState (VueUse source)
type AnyFn = (...args: any[]) => any
export function createGlobalState
(stateFactory: Fn): Fn {
let initialized = false
let state: any
const scope = effectScope(true)
return ((...args: any[]) => {
if (!initialized) {
state = scope.run(() => stateFactory(...args))!
initialized = true
}
return state
}) as Fn
}myCreateGlobalState (closure implementation)
function myCreateGlobalState
(stateFactory: Fn): Fn {
const state = stateFactory()
return (() => state) as Fn
}Usage Comparison
// VueUse version
export const useGlobalState = createGlobalState(() => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() { count.value++ }
return { count, doubleCount, increment }
})
// Closure version
export const useGlobalState = myCreateGlobalState(() => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() { count.value++ }
return { count, doubleCount, increment }
})In my project I replaced VueUse's createGlobalState with myCreateGlobalState and it worked fine, leading me to wonder whether the original hook is unnecessary.
Conclusion
VueUse implements createGlobalState using Vue's effectScope to create a global reactive state that can be accessed by multiple components. However, because the hook never calls stop , the primary benefit of effectScope —unified cleanup—does not apply here, and a simple closure could achieve the same result.
If anyone knows additional advantages of using effectScope in this context, please share your insights.
References
VueUse createGlobalState docs VueUse source code Vue effectScope API Related article
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.