Vue 3 状态共享:简单场景不用 Pinia 也能搞定

更新日期: 2025-12-03 阅读: 22 标签: 状态

vue 3 项目里,经常遇到需要在不同组件之间共享数据的情况。如果只是少量数据,其实完全没必要引入 Pinia 或 Vuex 这样完整的状态管理库。用更简单的方法,既能减少项目依赖,又能让代码更清晰。

下面介绍几种实用的方法,帮你解决常见的数据共享问题。


方法一:直接用 ref/reactive 导出(最常用)

这是最简单直接的方法。创建一个文件,在里面定义响应式数据,然后导出给其他组件使用。

1. 创建全局状态文件

// src/globalState.js
import { ref, reactive } from 'vue'

// 定义一个计数器
export const globalCount = ref(0)

// 定义一个用户对象
export const globalUser = reactive({
  name: '张三',
  age: 18,
  email: 'zhangsan@example.com'
})

// 定义一个主题设置
export const theme = ref('light')

2. 在组件中使用

<!-- UserProfile.vue -->
<script setup>
import { globalCount, globalUser } from '@/globalState'

function addAge() {
  globalUser.age++
}

function resetCount() {
  globalCount.value = 0
}
</script>

<template>
  <div>
    <h3>用户信息</h3>
    <p>姓名:{{ globalUser.name }}</p>
    <p>年龄:{{ globalUser.age }}</p>
    <button @click="addAge">增加年龄</button>
    
    <p>全局计数:{{ globalCount }}</p>
    <button @click="resetCount">重置计数</button>
  </div>
</template>

3. 在另一个组件中也使用

<!-- Counter.vue -->
<script setup>
import { globalCount } from '@/globalState'

function increment() {
  globalCount.value++
}
</script>

<template>
  <div>
    <p>当前计数:{{ globalCount }}</p>
    <button @click="increment">增加</button>
  </div>
</template>

这样做的好处:

  • 简单直接,不需要学习新概念

  • 数据是响应式的,一处修改,所有用到的地方自动更新

  • 适合跨组件、跨路由的少量数据共享

需要注意:

  • 数据是全局的,要小心命名冲突

  • 适合简单的场景,复杂逻辑可能不好维护


方法二:用自定义组合函数封装

如果你想对状态访问做一些控制,或者未来可能要扩展功能,可以用自定义组合函数来封装。

1. 创建组合函数

// src/composables/useGlobalData.js
import { ref, computed } from 'vue'

// 定义私有状态
const count = ref(0)
const todos = ref([])

// 导出组合函数
export function useGlobalData() {
  // 计算属性示例
  const todoCount = computed(() => todos.value.length)
  
  // 操作方法
  function addTodo(todo) {
    todos.value.push(todo)
  }
  
  function clearTodos() {
    todos.value = []
  }
  
  return {
    // 状态
    count,
    todos,
    
    // 计算属性
    todoCount,
    
    // 方法
    addTodo,
    clearTodos,
    increment: () => count.value++,
    decrement: () => count.value--,
    reset: () => count.value = 0
  }
}

2. 在组件中使用

<!-- TodoList.vue -->
<script setup>
import { useGlobalData } from '@/composables/useGlobalData'

const { 
  todos, 
  todoCount, 
  addTodo, 
  clearTodos 
} = useGlobalData()

const newTodo = ref('')

function handleAdd() {
  if (newTodo.value.trim()) {
    addTodo(newTodo.value)
    newTodo.value = ''
  }
}
</script>

<template>
  <div>
    <h3>待办事项 ({{ todoCount }})</h3>
    <input v-model="newTodo" @keyup.enter="handleAdd">
    <button @click="handleAdd">添加</button>
    <button @click="clearTodos">清空</button>
    
    <ul>
      <li v-for="(todo, index) in todos" :key="index">
        {{ todo }}
      </li>
    </ul>
  </div>
</template>

这种方式的优势:

  • 保持了组合式 api 的风格

  • 可以封装逻辑和计算方法

  • 未来要迁移到 Pinia 时,改动很小

  • 代码更有组织性


方法三:用 provide/inject(适合组件树内部共享)

如果数据只需要在某个组件及其子组件之间共享,用 provide/inject 更合适。这样不会污染全局。

1. 在父组件提供数据

<!-- App.vue -->
<script setup>
import { ref, provide } from 'vue'
import ChildComponent from './ChildComponent.vue'

// 定义要共享的数据
const theme = ref('light')
const userPreferences = ref({
  language: 'zh-CN',
  notifications: true
})

// 提供数据
provide('theme', theme)
provide('userPreferences', userPreferences)

// 提供修改方法
function toggleTheme() {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}

function updateLanguage(lang) {
  userPreferences.value.language = lang
}

provide('toggleTheme', toggleTheme)
provide('updateLanguage', updateLanguage)
</script>

<template>
  <div :class="theme">
    <ChildComponent />
  </div>
</template>

<style>
.light { background: white; color: black; }
.dark { background: #333; color: white; }
</style>

2. 在子组件中使用

<!-- ChildComponent.vue -->
<script setup>
import { inject } from 'vue'
import GrandChildComponent from './GrandChildComponent.vue'

// 注入数据
const theme = inject('theme')
const userPreferences = inject('userPreferences')
const toggleTheme = inject('toggleTheme')
const updateLanguage = inject('updateLanguage')
</script>

<template>
  <div>
    <p>当前主题:{{ theme }}</p>
    <p>语言设置:{{ userPreferences.language }}</p>
    <button @click="toggleTheme">切换主题</button>
    <button @click="() => updateLanguage('en-US')">
      切换为英文
    </button>
    
    <!-- 孙子组件也能拿到这些数据 -->
    <GrandChildComponent />
  </div>
</template>

3. 孙子组件也能用

<!-- GrandChildComponent.vue -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme')
const userPreferences = inject('userPreferences')
</script>

<template>
  <div>
    <p>深层的组件也能访问:{{ theme }} 主题</p>
    <p>通知设置:{{ userPreferences.notifications ? '开启' : '关闭' }}</p>
  </div>
</template>

适用场景:

  • 主题切换

  • 用户偏好设置

  • 多语言支持

  • 需要在组件树中传递,但不想用 props 一层层传

重要提醒:

  • 默认情况下,inject 拿到的不是响应式的

  • 要传响应式数据,必须传 ref 或 reactive 对象

  • 建议用 Symbol 作为 key,避免命名冲突


方法四:用事件总线(Event Bus)

Vue 3 移除了 $on、$off 等方法,但我们可以用 mitt 这样的小库来实现事件总线。

1. 安装 mitt

bash
npm install mitt

2. 创建事件总线

// src/eventBus.js
import mitt from 'mitt'

const emitter = mitt()

export default emitter

3. 发送事件

<!-- ComponentA.vue -->
<script setup>
import emitter from '@/eventBus'

function sendMessage() {
  emitter.emit('message', {
    text: '你好!',
    time: new Date()
  })
}
</script>

<template>
  <button @click="sendMessage">发送消息</button>
</template>

4. 接收事件

<!-- ComponentB.vue -->
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import emitter from '@/eventBus'

const messages = ref([])

function handleMessage(data) {
  messages.value.push(data)
}

onMounted(() => {
  emitter.on('message', handleMessage)
})

onUnmounted(() => {
  emitter.off('message', handleMessage)
})
</script>

<template>
  <div>
    <h3>收到的消息:</h3>
    <div v-for="(msg, index) in messages" :key="index">
      {{ msg.text }} - {{ msg.time }}
    </div>
  </div>
</template>

适合场景:

  • 组件之间需要通信,但没有直接关系

  • 一对多的通知

  • 简单的状态同步


实际项目中的选择建议

什么情况下用哪种方法?

1. 少量全局配置(如主题、用户信息)

  • 推荐:直接导出 ref/reactive

  • 理由:简单直接,不需要复杂逻辑

2. 需要封装的业务逻辑

  • 推荐:自定义组合函数

  • 理由:便于维护和测试,结构清晰

3. 组件树内部的共享

  • 推荐:provide/inject

  • 理由:作用域明确,不污染全局

4. 组件间简单通信

  • 推荐:事件总线

  • 理由:解耦,适合没有直接关系的组件

什么时候该用 Pinia?

虽然上面方法能满足大多数需求,但有些情况确实需要 Pinia:

  1. 数据复杂:有大量全局状态,涉及多个业务模块

  2. 需要高级功能:比如时间旅行调试、状态持久化

  3. 团队协作:需要统一的状态管理规范

  4. 复杂逻辑:有大量异步操作、中间件处理

  5. 需要插件:比如要集成请求库、表单验证等

性能考虑

  • 少量数据:上面方法性能都很好

  • 大量数据频繁更新:Pinia 可能有更好的优化

  • 组件层级深:provide/inject 性能比 props 逐层传递好


完整示例:用户登录状态管理

下面用一个实际例子展示如何管理用户登录状态:

// src/composables/useAuth.js
import { ref, computed } from 'vue'

// 状态
const user = ref(null)
const token = ref(localStorage.getItem('token') || '')

// 组合函数
export function useAuth() {
  // 计算属性
  const isLoggedIn = computed(() => !!token.value)
  const userName = computed(() => user.value?.name || '游客')
  
  // 方法
  async function login(username, password) {
    // 模拟登录请求
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ username, password })
    })
    
    const data = await response.json()
    
    if (data.success) {
      user.value = data.user
      token.value = data.token
      localStorage.setItem('token', data.token)
      return true
    }
    
    return false
  }
  
  function logout() {
    user.value = null
    token.value = ''
    localStorage.removeItem('token')
  }
  
  return {
    // 状态
    user,
    token,
    
    // 计算属性
    isLoggedIn,
    userName,
    
    // 方法
    login,
    logout
  }
}

总结

Vue 3 给了我们很多灵活的选择。关键是根据实际需求选择合适的方法:

  1. 简单场景:直接用 ref/reactive 导出,最省事

  2. 需要封装:用自定义组合函数,结构更好

  3. 组件树内共享:用 provide/inject,作用域明确

  4. 简单通信:用事件总线,解耦组件

记住一个原则:能用简单方法解决的,就不要用复杂方案。等真的需要 Pinia 那些高级功能时,再引入也不迟。

好的代码不是用最复杂的工具,而是用最合适的工具。根据你的项目大小和需求,选择最合适的状态管理方式,才能让项目既容易维护,又不会过度设计。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/13278

Javascript 状态管理工具 DataSet ,实现数据的订阅、查询、撤销和恢复

网页是用户与网站对接的入口,当我们允许用户在网页上进行一些频繁的操作时,对用户而言,误删、误操作是一件令人抓狂的事情,“如果时光可以倒流,这一切可以重来……”。

理解 React 轻量状态管理库 Unstated

在React写应用的时候,难免遇到跨组件通信的问题。现在已经有很多的解决方案。React本身的Context,Redux结合React-redux,Mobx结合mobx-react

你再也不用使用 Redux、Mobx、Flux 等状态管理了

这个库的作者希望使用 React 内置 API ,直接实现状态管理的功能。看完这个库的说明后,没有想到代码可以这个玩。短短几行代码,仅仅使用 React Hooks ,就实现了状态管理的功能。

为什么要使用状态管理

我们平时开发的大部分项目,由于复杂度不够, 很少使用 Vuex、Redux 等状态管理库,就算引入了 Vuex 这些库,也只是当作一个全局数据引用,并非对应用状态进行管理。但一旦页面的复杂度比较高,必然要引入状态管理,今天就聊聊我理解中的状态管理。

React使用Hooks与Context替代Redux状态管理

React Hooks 在 2018 年年底就已经公布了,正式发布是在 2019 年 5 月,关于它到底能做什么用,并不在本文的探讨范围之内,本文旨在摸索,如何基于 Hooks 以及 Context,实现多组件的状态共享,完成一个精简版的 Redux。

如何使用react hooks来进行状态管理?

首先要明确为什么要使用redux,这一点很重要,如果不知道为什么使用redux,那么在开发的过程中肯定不能合理的使用redux.首先来看redux的本质:redux做为一款状态管理工具,主要是为了解决组件间通信的问题。

Flutter基础--状态管理

当我们使用编译器创建一个新Flutter应用的时候,我们可以在主界面看到两个小部件StatelessWidget和StatefulWidget。这是两个最常见使用最频繁的小部件了。StatelessWidget ,StatefulWidget

共享可变状态中出现的问题以及如何避免?

本文回答了以下问题:么是共享可变状态?为什么会出现问题?如何避免其问题?标有(高级)的部分会更深入,如果你想更快地阅读本文,可以跳过。

使用Observable实现Vue全局状态共享

项目不大, 又不想用Vuex, 那么使用Observable来实现状态共享也不失为一个选择。用法 :让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象

node如何实现保持登录状态?

当我们登录成功,在这个页面刷新,页面并没有保存登录状态;今天我们就来看一下如何在后台使用cookie保存用户登录状态。做到刷新页面仍然显示在用户登录界面。node实现保持登录状态的方法如下:

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!