Vue3组合式API状态共享方案对比:从Pinia到EventBus的选型决策

Vue3状态共享的技术选型背景

Vue3的组合式API打破了Options API的数据作用域限制,setup()函数中声明的响应式变量默认只在当前组件可见。跨组件共享状态是前端工程化中绕不开的问题——从父子组件的简单传值到多模块的全局状态管理,方案跨度极大。选错方案轻则代码冗余,重则性能退化或调试困难。

状态共享方案的选型决策树:先问两个问题——共享范围有多大?状态变更频率有多高?两个组件间共享低频变更数据,Props/Provide就够了;多个模块高频读写同一份数据,必须用Pinia;跨组件的事件通知(非状态),EventBus最轻量。下面逐个方案展开对比。

Pinia:官方推荐的全局状态管理方案

Pinia是Vuex的继任者,也是Vue3官方推荐的状态管理库。核心优势:TypeScript类型推导开箱即用,无mutation的概念简化了API,支持模块化store和组合式函数写法。

基础用法——定义一个用户状态store:

// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  const token = ref('')
  const userInfo = ref<User>()
  const isLoggedIn = computed(() => !!token.value)

  async function login(username: string, password: string) {
    const res = await api.login({ username, password })
    token.value = res.token
    userInfo.value = res.user
  }

  function logout() {
    token.value = ''
    userInfo.value = undefined
  }

  return { token, userInfo, isLoggedIn, login, logout }
})

组件中使用:

<script setup lang="ts">
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// 解构需要storeToRefs保持响应性
const { isLoggedIn, userInfo } = storeToRefs(userStore)
const { login } = userStore  // 方法直接解构
</script>

Pinia的$subscribe方法可以监听整个store的变更,配合localStorage实现持久化:

userStore.$subscribe((mutation, state) => {
  localStorage.setItem('user', JSON.stringify({
    token: state.token,
    userInfo: state.userInfo
  }))
})

Provide/Inject:组件树范围内的依赖注入

当状态共享范围限定在某棵组件子树内,Pinia的全局store过于重。Provide/Inject是Vue3原生机制,无需引入第三方库,配合Symbol作为key防止命名冲突。

// 父组件提供状态
import { provide, ref, readonly } from 'vue'

const theme = ref('light')
const toggleTheme = () => {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}

// 用readonly包裹防止子组件直接修改
provide(ThemeKey, {
  theme: readonly(theme),
  toggleTheme
})

// 子组件注入
const { theme, toggleTheme } = inject(ThemeKey)

关键实践:提供的状态必须用readonly包裹,修改逻辑通过注入的方法完成。这保证了数据流向的可追踪性——如果子组件直接修改注入的ref,调试时无法追踪变更来源。响应式布局中组件树层级的主题切换是Provide/Inject的经典场景。

EventBus:跨组件事件通知的轻量方案

EventBus解决的不是状态共享,而是事件通知。典型场景:全局搜索框输入后通知结果列表刷新、用户权限变更后通知所有打开的标签页。Vue3移除了$on/$emit事件总线,社区有两个替代方案。

方案一:用mitt库(150字节,API与Vue2 EventBus一致):

// eventBus.ts
import mitt from 'mitt'
export const bus = mitt<{
  'search:update': string
  'permission:change': string[]
}>()

// 发送端
bus.emit('search:update', keyword)

// 接收端
bus.on('search:update', (keyword) => {
  fetchResults(keyword)
})

// 必须在onUnmounted中移除监听
onUnmounted(() => {
  bus.off('search:update')
})

方案二:用Vue3的reactiveref配合watch实现类似效果,优势是不引入新依赖:

const searchKeyword = ref('')

// 组件A修改
searchKeyword.value = 'new query'

// 组件B监听
watch(searchKeyword, (newVal) => {
  fetchResults(newVal)
})

方案对比与选型决策矩阵

四种方案从六个维度对比:

| 维度 | Pinia | Provide/Inject | mitt EventBus | 响应式ref共享 |
|——|——-|—————|————–|————-|
| 共享范围 | 全局 | 组件子树 | 全局 | 手动控制 |
| TypeScript支持 | 优秀 | 一般 | 需手动标注 | 优秀 |
| DevTools调试 | 支持 | 不支持 | 不支持 | 不支持 |
| 状态持久化 | 内置插件 | 手动 | 不适用 | 手动 |
| 学习成本 | 中等 | 低 | 低 | 低 |
| 适用规模 | 中大型 | 小型 | 不限 | 小型 |

决策路径总结:全局共享+需要DevTools调试→Pinia;组件子树共享→Provide/Inject+readonly;跨组件事件通知→mitt EventBus;简单场景两个组件间共享→composable函数封装ref。前端开发中不要过度工程化——两个组件间传数据就引入Pinia,好比用挖掘机挖花盆。组件库设计时尤其要注意,组件库不应强制依赖Pinia,Provide/Inject才是组件库状态传递的标准做法。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-zhuang-tai-gong-xiang-fang-an-dui-bi/

(0)
小编小编
上一篇 18小时前
下一篇 18小时前

相关推荐