说明
createGlobalState: 在全局作用域中保留状态,以便被 vue 实例复用。
// store.js
import { createGlobalState } from "@vueuse/core";
import { ref } from "vue";
export const useGlobalState = createGlobalState(() => {
const count = ref(0);
return { count };
});
// demo.vue
import { useGlobalState } from "./store";
const { count } = useGlobalState();
用于组件之间的状态共享。
源码
import { effectScope } from 'vue-demi'
import type { AnyFn } from '../utils'
/**
* Keep states in the global scope to be reusable across Vue instances.
*
* @see https://vueuse.org/createGlobalState
* @param stateFactory A factory function to create the state
*/
export function createGlobalState<Fn extends AnyFn>(
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
}
源码中使用了 effectScope 来声明 state。
创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。——vue 官网
思考
为什么使用 effectScope?
Vue3 组件状态共享可以使用 export const state = ref() 来实现。
// store.js
import { ref } from "vue";
export const count = ref(0);
// demo.vue
import { count } from './store'
那为什么 VueUse 要封装 createGlobalState 函数呢?为什么用 effectScope 来实现 createGlobalState 函数呢?
官网对 effectScope 描述很简单,并没有详细说明他的使用场景,能解决什么问题。
RFC 上有对 effectScope 的详细说明:
vue 的 setup 中声明的响应会被收集起来,在组件卸载时会自动取消响应的追踪。在组件之外或作为独立包使用时,需要处理 computed 和 watch 的 effects。通过 effectScope 的 stop 方法可以 effectScope 函数参数中的所有 effects。
“组件之外或作为独立包使用”的场景没有细说。
createGlobalState 源码中并没有提供外部访问 scope 的 stop 方法,采用 effectScope 并不是为了在组件之外或作为独立包使用时统一处理 computed 和 watch 的 effects。
而是为了解决 export const state = ref() 的另一个问题,上面提到组件卸载时会自动取消响应的追踪,也就是会自动取消 computed 和 watch 的 effects,在 RFC 上也提到了这个问题。
// uesCount.js
let count: Ref<number>;
let double: ComputedRef<number>;
export function useCount() {
if (!count) {
count = ref(0);
watch(count, () => {
console.log("watch count");
});
double = computed(() => {
return count.value * 2;
});
}
return { count, double };
}
// demo1.vue
<script setup lang="ts">
import { useCount } from "./uesCount";
const { count, double } = useCount();
</script>
<template>
<button type="button" @click="count++">count is {{ count }}</button>
double is {{ double }}
</template>
demo1 和 demo2 都用到到 count、double。在两个组件都渲染时,其中一个组件卸载导致另一组件的 watch 和 double 失效。
VueUse 使用 effectScope 来实现全局状态就是为了解决这个问题。VueUse 的 createGlobalState 是对 export const state = ref() 的扩展。
VueUse createGlobalState 和 Pinia 有什么区别呢?
Pinia 官网介绍说明了他们的区别:
Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。 如果您熟悉 Composition API,您可能会认为您已经可以通过一个简单的 export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,会使您的应用程序暴露于安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:
- dev-tools 支持
- 跟踪动作、突变的时间线
- Store 出现在使用它们的组件中
- time travel 和 更容易的调试
- 热模块更换
- 在不重新加载页面的情况下修改您的 Store
- 在开发时保持任何现有状态
- 插件:使用插件扩展 Pinia 功能
- 为 JS 用户提供适当的 TypeScript 支持或 autocompletion
- 服务器端渲染支持
Pinia 在服务器端渲染、状态的追踪调试、热更新上更好些。