稀土掘金技术社区 01月02日
在做项目的过程中,发现 VueUse 一个很鸡肋的 hook
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文探讨了在Vue项目中管理全局状态的新方法,起因是作者在工作中发现同事巧妙地使用hook封装弹窗组件。文章深入分析了vueuse库中的createGlobalState函数,该函数利用Vue的effectScope进行依赖收集,但作者认为对于全局状态管理而言,effectScope的停止追踪依赖功能并非必要,简单闭包即可实现相同效果。通过对比vueuse的实现和自定义的闭包实现,作者提出了对createGlobalState的质疑,并引发了关于effectScope在全局状态管理中实际意义的思考。

💡 弹窗组件封装:通过hook封装弹窗的显示状态、props和组件本身,并暴露切换方法,这种方式有效避免了大量重复代码,提升了代码的可维护性。

🌐 全局状态管理:在Vue项目中,针对同级弹窗组件间的状态共享,作者同事使用了vueuse的createGlobalState hook,它能方便地在不同组件间共享状态。

🤔 effectScope的意义:文章深入源码分析,发现vueuse的createGlobalState利用effectScope收集依赖,但并未调用其stop方法。作者质疑此举的必要性,认为简单闭包也能实现相同的全局状态共享,并提出了对effectScope在全局状态管理中作用的疑问。

🔄 闭包实现对比:文章提供了闭包实现的全局状态管理代码,并与vueuse的实现进行对比,指出二者在功能上并无差异,进一步加强了作者的观点。

原创 一路向北wow 2025-01-02 08:30 重庆

点击关注公众号,“技术干货”及时达!

点击关注公众号,“技术干货” 及时达!

背景

最近刚入职一家公司,在项目中发现了一个非常巧妙的展示弹窗的方式:
首先我们想想,不管一个弹窗长什么样子,它都会有一个控制显示的 「visible」,父组件传进来的 「props」,还有就是 「弹窗组件本身」,基于这一点我的这位同事把它们封装到一个 hook 当中,并且暴露一个 onToggleComponent 方法切换组件。然后在一个公共组件内调用这个 hook 拿到这些公共状态,再用一个动态组件把这个弹窗显示出来,并且绑定对应的状态与事件。
我还真没这样用过,可能是之前接触到的项目太小,一个页面内也就那几个弹窗,不像现在有十几个弹窗,如果不封装 hook,光这些弹窗的显示逻辑都要占几十行代码了,还都是重复的。不得不说,这种方式确实是妙!?
细心的 「jym」 已经发现这其中涉及到一个问题:「公共状态」
那么在 「vue」 项目中,当我们有很多组件需要共享状态时,你能想到几种方式?

共享状态的几种常用方式

1、props、emit(父与子共享状态)
2、provide,inject(多组件)
3、使用状态库,比如:vuex、pinia
4、发布订阅
5、......

以上是常用的几种方式,但是这里需要共享状态的是一堆同级弹窗组件,并且同一时间只有一个弹窗显示,也就是同一时间只有一个弹窗组件会用到这些状态,我的这位同事使用的是 vueuse 中一个创建全局状态的 hook:「createGlobalState」
vueuse官方文档中对它的介绍:

然后我先了解了一下它的使用:

createGlobalState 的使用

详细请查看:https://vueuse.nodejs.cn/shared/createGlobalState/

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 }
  }
)

这样其他不同组件就可以通过调用 useGlobalState 获取 count, doubleCount, increment 这三个状态,实现了状态共享。
非常方便是不是,然后我就想去了解一下 vueuse 是怎么实现的。下面是源码:
从官网进入源码发现实现并不复杂,函数体只有十几行代码,他是在一个闭包内保存了这些状态并返回一个 hook 去获取它们。但是这里又引出了一个新的 api,就是 vue 的 「effectScope」

effectScope的介绍与使用

// 类型
function effectScope(detached?: boolean): EffectScope
interface EffectScope 
{
  run<T>(fn: () => T): T | undefined // 如果作用域不活跃就为 undefined
  stop(): void
}
// 示例
const scope = effectScope()
scope.run(() => {
  const doubled = computed(() => counter.value * 2)
  watch(doubled, () => console.log(doubled.value))
  watchEffect(() => console.log('Count: ', doubled.value))
})
// 处理掉当前作用域内的所有 effect
scope.stop()

effectScope 函数会返回一个作用域对象,对象上有一个 run 方法和一个 stop方法,run 方法接受一个状态工厂函数,它会统一收集内部产生的依赖到一个作用域内。调用 stop 函数处理掉当前作用域内的所有 effect,停止追踪状态变化。
官方文档:https://cn.vuejs.org/api/reactivity-advanced.html#effectscope
更详细的讲解:https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md

思考

effectScope 为我们提供了「手动统一收集依赖」,并且「统一停止追踪依赖」变化的语法糖。
vueuse 利用 vue 的 effectScope 统一收集了依赖,但是通过 createGlobalState 的源码来看他并「没有调用stop」函数去停止追踪依赖变化。
那既然你不需要统一停止追踪,那统一收集到一个作用域又有什么意义?直接使用一个简单闭包不是也能实现相同的效果,以下是闭包与effectScope两种实现方式的对比:

createGlobalState(vueuse源码)

type AnyFn = (...args: any[]) => any
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
}

myCreateGlobalState(闭包实现)

function myCreateGlobalState<Fn extends AnyFn>(stateFactory: Fn): Fn {
  const state = stateFactory()
  return (() => state) as Fn
}

两种实现的使用示例

// vueuse createGlobalState
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 }
  }
)
// 闭包方式
export const useGlobalState = myCreateGlobalState(
  () => {
    // state
    const count = ref(0)
    // getters
    const doubleCount = computed(() => count.value * 2)
    // actions
    function increment({
      count.value++
    }
    return { count, doubleCount, increment }
  }
)

并且我在公司项目中把 vueuse 的 createGlobalState 函数更换为我的 myCreateGlobalState,依然能正常使用,那我是不是可以认为 createGlobalState 就是个很鸡肋的 hook?。

总结

vueuse 通过 vue 的 「effectScope」 实现了 「createGlobalState」,用来创建全局状态,方便多组件取用。
但是我觉得 effectScope 的意义在于统一停止追踪依赖变化。createGlobalState 是为了创建全局状态,并不需要停止追踪依赖。那就没必要使用 effectScope 去实现,简单闭包实现即可。
这是我的理解,也是我的疑惑,如果有大佬知道使用 effectScope 实现的好处或意义,请在评论区指教?‍♂️(抱拳)

参考文章

https://vueuse.nodejs.cn/shared/createGlobalState/
https://github.com/vueuse/vueuse/blob/main/packages/shared/createGlobalState/index.ts
https://cn.vuejs.org/api/reactivity-advanced.html#effectscope
https://juejin.cn/post/7412922729517203465

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Vue 全局状态管理 effectScope createGlobalState vueuse
相关文章