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 创建的作用域会自动追踪其内部的副作用函数,并在作用域被销毁时自动清理这些副作用。这样可以避免内存泄漏和不必要的副作用继续运行。
相关推荐
Moment9 分钟前
面试官:一个接口使用postman这些测试很快,但是页面加载很慢怎么回事 😤😤😤
前端·后端·面试
诗书画唱12 分钟前
【前端面试题】JavaScript 核心知识点解析(第二十二题到第六十一题)
开发语言·前端·javascript
excel19 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子26 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构32 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep34 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss38 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风38 分钟前
html二次作业
前端·html
江城开朗的豌豆42 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵42 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae