createGlobalState
当开发Vue.js应用程序时,通常需要在多个组件之间共享状态或数据,此时大多数同学会选择Vuex或者Pinia,但是在小型项目开发中,我们并不需要用到所有的第三方插件功能,我们只需要使用 createGlobalState
来创建一个全局状态,就可以轻松在组件中实现数据状态共享的功能。
createGlobalState
函数的作用是创建一个可在整个应用程序中共享的全局状态。它接受一个函数作为参数,并返回一个函数可供多个组件调用获取数据状态进行访问,它的实现原理就是通过闭包缓存状态。
通过使用createGlobalState
函数,您可以避免在组件之间手动传递状态,而是将状态存储在一个全局变量中。这样,任何一个组件都可以访问和修改这个全局状态,而无需通过props或事件传递。
使用createGlobalState
函数的一般流程如下:
- 使用
createGlobalState
创建一个状态管理函数useGlobalState
。 - 在需要访问全局状态的组件中,使用
useGlobalState
函数来获取该全局状态的引用。
下面是一个简单的示例,演示了如何使用createGlobalState
函数创建和使用全局状态:
typescript
// globalState.ts
import { createGlobalState } from './index'
export const useGlobalState = createGlobalState(() => {
const counter = ref(0)
function increment() {
counter.value++
}
function decrement() {
counter.value--
}
return {
counter,
increment,
decrement
}
})
在上面的示例中,我们使用createGlobalState
函数创建了一个名为useCounter
的全局状态,并将其初始值设置为0。然后,在两个不同的组件中使用useGlobalState
函数来获取该全局状态的引用,以便可以在组件中使用,接下来我们来实现一下 createGlobalState
函数。
effectScope
在这之前,我们需要了解一下Vue3中的 effectScope
,effectScope
是 Vue3 的进阶API,它可以用于创建一个 effect
作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。
在 Vue 2 中,使用 watch
或 computed
时,副作用会在组件实例的整个生命周期中持续存在。这可能导致一些不必要的副作用执行,尤其是在大型应用中。
Vue 3 引入了 effectScope
,允许我们创建一个局部作用域,将副作用限制在该作用域内。这样,当作用域销毁时,副作用也会自动清理,避免不必要的执行。
effectScope
主要有两个方法:run
和 stop
。我们可以在 run
方法中定义需要在作用域内执行的副作用函数,然后在适当的时候使用 stop
方法停止该作用域,同时它会在该作用域销毁时自动清理副作用函数。
以下是一个示例,演示了如何使用 effectScope
:
typescript
import { effectScope, ref, watch, watchEffect, computed } from 'vue'
const counter = ref(1)
function createEffect() {
const scope = effectScope()
scope.run(() => {
const doubled = computed(() => counter.value * 2)
watch(
() => doubled.value,
() => console.log('watch', doubled.value),
{
flush: 'sync'
}
)
watchEffect(() => console.log('watchEffect: ', doubled.value))
})
return scope
}
// 创建作用域
const effectScope1 = createEffect()
// 改变响应式数据,触发副作用执行
counter.value++
// 停止作用域,副作用将被清理
effectScope1.stop()
setTimeout(() => {
// effect 作用域已经被停止,此次修改不会再触发副作用
counter.value++
}, 2000)
通过使用 effectScope
,我们可以控制副作用的执行时机,并确保在不需要时进行清理,提高了性能和代码可读性。
createGlobalState
现在我们已经了解了 effectScope
的用法,接下来我们就可以实现 createGlobalState
函数了。
实现
我们先在不使用 effectScope
的情况下实现一下 createGlobalState
函数:
typescript
/**
* 创建一个全局状态管理函数
* @param {T} stateFactory
* @returns {T}
*/
export function createGlobalState<T extends (...args: any[]) => any>(
stateFactory: T
): T {
// 标识是否已经初始化
let initialized = false
// 保存状态
let state: any
/**
* 返回一个函数,该函数在第一次调用时初始化状态,后续调用时返回已经初始化的状态
*/
return ((...args: any[]) => {
if (!initialized) {
state = stateFactory(...args)
initialized = true
}
return state
}) as T
}
我们在上面完成了一个createGlobalState
函数,它的作用是创建一个全局状态管理函数。它接受一个stateFactory
参数,该参数是一个函数类型,用于创建并返回初始状态。
该函数内部定义了两个变量:initialized
和state
。initialized
用于标识是否已经初始化状态,初始值为false
。state
用于保存状态。
返回的函数是一个闭包,它接受和stateFactory
一样的参数。在第一次调用该函数时,它会执行stateFactory
函数并传入相应的参数,将返回值赋给state
变量,并将initialized
标记为true
。然后,它会返回已经初始化的状态。
从第二次调用开始,返回的函数只会返回已经初始化的状态,而不再执行stateFactory
。这样可以确保在整个应用程序中使用同一个状态,而不是每次调用都重新创建一个新的状态。
通过这个函数,你可以方便地创建一个全局可访问的状态,并在需要的地方获取和更新该状态,而无需手动管理状态的初始化和共享。
使用
我们下面使用createGlobalState
函数来创建一个全局状态,并在两个组件中使用它。
typescript
// useCounterState.ts
import { ref } from 'vue'
import { createGlobalState } from '../index'
export const useCounterState = createGlobalState(() => {
const counter = ref(0)
const increment = () => counter.value++
const decrement = () => counter.value--
return {
counter,
increment,
decrement
}
})
我们在两个组件中使用useCounterState
函数来获取全局状态的引用,并在组件中使用它。
- Comp1.vue
html
<template>
<div>
<n-space>
<n-button type="primary" @click="increment">increment</n-button>
<n-button type="primary" @click="decrement">decrement</n-button>
</n-space>
<p>Comp1: {{ counter }}</p>
</div>
</template>
<script lang="ts" setup>
import { useCounterState } from './store/useCounterState'
const { counter, increment, decrement } = useCounterState()
</script>
- Comp2.vue
html
<template>
<div>
<p>Comp2: {{ counter }}</p>
</div>
</template>
<script lang="ts" setup>
import { useCounterState } from './store/useCounterState'
const { counter } = useCounterState()
</script>
- App.vue
html
<template>
<div>
<Comp1 />
<Comp2 />
</div>
</template>
<script lang="ts" setup>
import Comp1 from './components/Comp1.vue'
import Comp2 from './components/Comp2.vue'
</script>
我们来看一下它的效果:
优化
我们在上面实现的 createGlobalState
函数有一些问题,比如在某些情况下,我们需要清理这个全局状态,不在对它进行监听,但是我们目前无法清理这个全局状态,因为我们无法获取到它的 effect
,所以我们接下来需要使用 effectScope
对它进行一些优化。
typescript
import { effectScope } from 'vue'
export function createGlobalState<T extends (...args: any[]) => any>(
stateFactory: T
): T {
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 T
}
在这段代码中,使用了Vue 3的effectScope
函数来创建一个作用域,它的作用是将响应式的副作用函数(effect)限定在特定的作用域范围内。使用effectScope
可以实现以下几个优点:
-
隔离副作用 :
effectScope
可以将副作用函数限定在特定的作用域中运行,这样可以隔离不同作用域中的副作用,避免相互之间的干扰。在示例代码中,通过在作用域中运行stateFactory
函数,确保了创建全局状态的副作用被隔离。 -
自动清理副作用 :使用
effectScope
创建的作用域会自动追踪其内部的副作用函数,并在作用域被销毁时自动清理这些副作用。这样可以避免内存泄漏和不必要的副作用继续运行。在示例代码中,当scope
作用域被销毁时,与之关联的stateFactory
副作用也会被清理。
总之,尽管在示例代码中也可以不使用effectScope
来实现相同的功能,但是使用effectScope
可以提供更好的代码隔离性、副作用的自动清理以及性能优化,特别是在处理全局状态和副作用时,使用effectScope
可以更好地结合Vue 3的响应式系统。
本节收获
- 通过手写
createGlobalState
函数,可以方便地创建一个全局可访问的状态,并在需要的地方获取和更新该状态,而无需手动管理状态的初始化和共享。 - 通过学习
effectScope
可以将副作用函数限定在特定的作用域中运行,这样可以隔离不同作用域中的副作用,避免相互之间的干扰。 - 通过使用
effectScope
创建的作用域会自动追踪其内部的副作用函数,并在作用域被销毁时自动清理这些副作用。这样可以避免内存泄漏和不必要的副作用继续运行。