VueUse 之 createGlobalState

createGlobalState

当开发Vue.js应用程序时,通常需要在多个组件之间共享状态或数据,此时大多数同学会选择Vuex或者Pinia,但是在小型项目开发中,我们并不需要用到所有的第三方插件功能,我们只需要使用 createGlobalState 来创建一个全局状态,就可以轻松在组件中实现数据状态共享的功能。

createGlobalState函数的作用是创建一个可在整个应用程序中共享的全局状态。它接受一个函数作为参数,并返回一个函数可供多个组件调用获取数据状态进行访问,它的实现原理就是通过闭包缓存状态。

通过使用createGlobalState函数,您可以避免在组件之间手动传递状态,而是将状态存储在一个全局变量中。这样,任何一个组件都可以访问和修改这个全局状态,而无需通过props或事件传递。

使用createGlobalState函数的一般流程如下:

  1. 使用 createGlobalState 创建一个状态管理函数 useGlobalState
  2. 在需要访问全局状态的组件中,使用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中的 effectScopeeffectScope是 Vue3 的进阶API,它可以用于创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。

在 Vue 2 中,使用 watchcomputed 时,副作用会在组件实例的整个生命周期中持续存在。这可能导致一些不必要的副作用执行,尤其是在大型应用中。

Vue 3 引入了 effectScope,允许我们创建一个局部作用域,将副作用限制在该作用域内。这样,当作用域销毁时,副作用也会自动清理,避免不必要的执行。

effectScope 主要有两个方法:runstop。我们可以在 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参数,该参数是一个函数类型,用于创建并返回初始状态。

该函数内部定义了两个变量:initializedstateinitialized用于标识是否已经初始化状态,初始值为falsestate用于保存状态。

返回的函数是一个闭包,它接受和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可以实现以下几个优点:

  1. 隔离副作用effectScope可以将副作用函数限定在特定的作用域中运行,这样可以隔离不同作用域中的副作用,避免相互之间的干扰。在示例代码中,通过在作用域中运行stateFactory函数,确保了创建全局状态的副作用被隔离。

  2. 自动清理副作用 :使用effectScope创建的作用域会自动追踪其内部的副作用函数,并在作用域被销毁时自动清理这些副作用。这样可以避免内存泄漏和不必要的副作用继续运行。在示例代码中,当scope作用域被销毁时,与之关联的stateFactory副作用也会被清理。

总之,尽管在示例代码中也可以不使用effectScope来实现相同的功能,但是使用effectScope可以提供更好的代码隔离性、副作用的自动清理以及性能优化,特别是在处理全局状态和副作用时,使用effectScope可以更好地结合Vue 3的响应式系统。

本节收获

  1. 通过手写 createGlobalState 函数,可以方便地创建一个全局可访问的状态,并在需要的地方获取和更新该状态,而无需手动管理状态的初始化和共享。
  2. 通过学习 effectScope 可以将副作用函数限定在特定的作用域中运行,这样可以隔离不同作用域中的副作用,避免相互之间的干扰。
  3. 通过使用 effectScope 创建的作用域会自动追踪其内部的副作用函数,并在作用域被销毁时自动清理这些副作用。这样可以避免内存泄漏和不必要的副作用继续运行。
相关推荐
花海少爷8 分钟前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
Amd79412 分钟前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You21 分钟前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生32 分钟前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互
sinat_3842410935 分钟前
在有网络连接的机器上打包 electron 及其依赖项,在没有网络连接的机器上安装这些离线包
javascript·arcgis·electron
baiduopenmap1 小时前
百度世界2024精选公开课:基于地图智能体的导航出行AI应用创新实践
前端·人工智能·百度地图
loooseFish1 小时前
小程序webview我爱死你了 小程序webview和H5通讯
前端
小牛itbull1 小时前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
菜牙买菜1 小时前
让安卓也能玩出Element-Plus的表格效果
前端
请叫我欧皇i1 小时前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript