Vue3 状态管理完全指南:从响应式 API 到 Pinia

作者:90后晨仔日期:2025/10/19

什么是状态管理?

在 Vue 开发中,状态管理是一个核心概念。简单来说,状态就是驱动应用的数据源。每一个 Vue 组件实例都在管理自己的响应式状态,让我们从一个简单的计数器组件开始理解:

1<script setup>
2import { ref } from 'vue'
3
4// 状态 - 驱动应用的数据源
5const count = ref(0)
6
7// 动作 - 修改状态的方法
8function increment() {
9  count.value++
10}
11</script>
12
13<!-- 视图 - 状态的声明式映射 -->
14<template>{{ count }}</template>
15

这个简单的例子展示了状态管理的三个核心要素:

  • 状态:数据源 (count)
  • 视图:状态的声明式映射 (模板)
  • 动作:状态变更的逻辑 (increment)

这就是所谓的"单向数据流"概念。

为什么需要状态管理?

当应用变得复杂时,我们会遇到两个典型问题:

问题 1:多个组件共享状态

1<!-- ComponentA.vue -->
2<template>组件 A: {{ count }}</template>
3
4<!-- ComponentB.vue -->  
5<template>组件 B: {{ count }}</template>
6

如果多个视图依赖于同一份状态,传统的解决方案是通过 props 逐级传递,但这在深层次组件树中会变得非常繁琐,导致 Prop 逐级透传问题

问题 2:多组件修改同一状态

来自不同视图的交互都需要更改同一份状态时,直接通过事件或模板引用会导致代码难以维护。

解决方案:将共享状态抽取到全局单例中管理。

使用响应式 API 实现简单状态管理

Vue 的响应式系统本身就提供了状态管理的能力。

创建全局状态 Store

1// store.js
2import { reactive } from 'vue'
3
4export const store = reactive({
5  count: 0,
6  user: null,
7  todos: []
8})
9

在组件中使用

1<!-- ComponentA.vue -->
2<script setup>
3import { store } from './store.js'
4</script>
5
6<template>
7  <div>From A: {{ store.count }}</div>
8  <button @click="store.count++">+1</button>
9</template>
10
1<!-- ComponentB.vue -->
2<script setup>
3import { store } from './store.js'
4</script>
5
6<template>
7  <div>From B: {{ store.count }}</div>
8  <button @click="store.count++">+1</button>
9</template>
10

问题:任意修改的风险

上面的实现有个问题:任何导入 store 的组件都可以随意修改状态,这在大型应用中难以维护。

改进:封装状态修改逻辑

1// store.js
2import { reactive } from 'vue'
3
4export const store = reactive({
5  // 状态
6  count: 0,
7  user: null,
8  todos: [],
9  
10  // 动作 - 封装状态修改逻辑
11  increment() {
12    this.count++
13  },
14  
15  setUser(user) {
16    this.user = user
17  },
18  
19  addTodo(todo) {
20    this.todos.push(todo)
21  },
22  
23  removeTodo(id) {
24    this.todos = this.todos.filter(todo => todo.id !== id)
25  }
26})
27
1<!-- ComponentB.vue -->
2<script setup>
3import { store } from './store.js'
4</script>
5
6<template>
7  <button @click="store.increment()">
8    From B: {{ store.count }}
9  </button>
10</template>
11

注意:这里使用 store.increment() 带圆括号调用,因为它不是组件方法,需要正确的 this 上下文。

使用组合式函数管理状态

1// useCounter.js
2import { ref } from 'vue'
3
4// 全局状态
5const globalCount = ref(1)
6
7export function useCount() {
8  // 局部状态
9  const localCount = ref(1)
10  
11  function incrementGlobal() {
12    globalCount.value++
13  }
14  
15  function incrementLocal() {
16    localCount.value++
17  }
18  
19  return {
20    globalCount: readonly(globalCount), // 使用 readonly 保护全局状态
21    localCount,
22    incrementGlobal,
23    incrementLocal
24  }
25}
26

Pinia:现代化的状态管理库

虽然手动状态管理在简单场景中足够,但生产级应用需要更多功能:

  • 团队协作约定
  • Vue DevTools 集成
  • 模块热更新
  • 服务端渲染支持
  • 完善的 TypeScript 支持

Pinia 是 Vue 官方推荐的状态管理库,它解决了上述所有问题。

为什么选择 Pinia?

  • 类型安全:完美的 TypeScript 支持
  • DevTools 支持:时间旅行调试等
  • 模块热更新:开发时保持状态
  • 简洁的 API:学习成本低
  • 组合式 API:与 Vue 3 完美契合

安装和配置

1npm install pinia
2
1// main.js
2import { createApp } from 'vue'
3import { createPinia } from 'pinia'
4import App from './App.vue'
5
6const pinia = createPinia()
7const app = createApp(App)
8
9app.use(pinia)
10app.mount('#app')
11

创建 Store

选项式 Store

1// stores/counter.js
2import { defineStore } from 'pinia'
3
4export const useCounterStore = defineStore('counter', {
5  // 状态
6  state: () => ({
7    count: 0,
8    user: null
9  }),
10  
11  // 计算属性
12  getters: {
13    doubleCount: (state) => state.count * 2,
14    isAuthenticated: (state) => state.user !== null
15  },
16  
17  // 动作
18  actions: {
19    increment() {
20      this.count++
21    },
22    async login(credentials) {
23      const user = await api.login(credentials)
24      this.user = user
25    },
26    logout() {
27      this.user = null
28    }
29  }
30})
31

组合式 Store(推荐)

1// stores/counter.js
2import { defineStore } from 'pinia'
3import { ref, computed } from 'vue'
4
5export const useCounterStore = defineStore('counter', () => {
6  // 状态
7  const count = ref(0)
8  const user = ref(null)
9  
10  // 计算属性
11  const doubleCount = computed(() => count.value * 2)
12  const isAuthenticated = computed(() => user.value !== null)
13  
14  // 动作
15  function increment() {
16    count.value++
17  }
18  
19  async function login(credentials) {
20    const response = await fetch('/api/login', {
21      method: 'POST',
22      body: JSON.stringify(credentials)
23    })
24    user.value = await response.json()
25  }
26  
27  function logout() {
28    user.value = null
29  }
30  
31  return {
32    count,
33    user,
34    doubleCount,
35    isAuthenticated,
36    increment,
37    login,
38    logout
39  }
40})
41

在组件中使用 Store

1<script setup>
2import { useCounterStore } from '@/stores/counter'
3import { storeToRefs } from 'pinia'
4
5const counterStore = useCounterStore()
6
7// 使用 storeToRefs 保持响应式并解构
8const { count, doubleCount, isAuthenticated } = storeToRefs(counterStore)
9const { increment, login } = counterStore
10
11// 直接修改状态(不推荐)
12const directIncrement = () => {
13  counterStore.count++
14}
15
16// 使用 action(推荐)
17const actionIncrement = () => {
18  counterStore.increment()
19}
20
21// 批量修改
22const patchUpdate = () => {
23  counterStore.$patch({
24    count: counterStore.count + 1,
25    user: { name: 'Updated User' }
26  })
27}
28
29// 重置状态
30const resetStore = () => {
31  counterStore.$reset()
32}
33
34// 订阅状态变化
35counterStore.$subscribe((mutation, state) => {
36  console.log('状态变化:', mutation)
37  console.log('新状态:', state)
38})
39</script>
40
41<template>
42  <div>
43    <p>计数: {{ count }}</p>
44    <p>双倍计数: {{ doubleCount }}</p>
45    <p>认证状态: {{ isAuthenticated ? '已登录' : '未登录' }}</p>
46    
47    <button @click="increment">增加</button>
48    <button @click="directIncrement">直接增加</button>
49    <button @click="patchUpdate">批量更新</button>
50    <button @click="resetStore">重置</button>
51  </div>
52</template>
53

在 Store 之间使用其他 Store

1// stores/auth.js
2import { defineStore } from 'pinia'
3
4export const useAuthStore = defineStore('auth', () => {
5  const user = ref(null)
6  const token = ref('')
7  
8  function setAuth(userData, authToken) {
9    user.value = userData
10    token.value = authToken
11  }
12  
13  return { user, token, setAuth }
14})
15
16// stores/todos.js  
17import { defineStore } from 'pinia'
18import { useAuthStore } from './auth'
19
20export const useTodosStore = defineStore('todos', () => {
21  const authStore = useAuthStore()
22  const todos = ref([])
23  
24  async function fetchTodos() {
25    // 使用其他 store 的状态
26    if (!authStore.token) {
27      throw new Error('未认证')
28    }
29    
30    const response = await fetch('/api/todos', {
31      headers: {
32        Authorization: `Bearer ${authStore.token}`
33      }
34    })
35    todos.value = await response.json()
36  }
37  
38  return { todos, fetchTodos }
39})
40

高级模式和最佳实践

1. 数据持久化

1// plugins/persistence.js
2import { watch } from 'vue'
3
4export function persistStore(store, key = store.$id) {
5  // 从 localStorage 恢复状态
6  const persisted = localStorage.getItem(key)
7  if (persisted) {
8    store.$patch(JSON.parse(persisted))
9  }
10  
11  // 监听状态变化并保存
12  watch(
13    () => store.$state,
14    (state) => {
15      localStorage.setItem(key, JSON.stringify(state))
16    },
17    { deep: true }
18  )
19}
20
21// 在 store 中使用
22export const usePersistedStore = defineStore('persisted', () => {
23  const state = ref({})
24  
25  // 在 store 创建后调用
26  onMounted(() => {
27    persistStore(usePersistedStore())
28  })
29  
30  return { state }
31})
32

2. API 集成模式

1// stores/posts.js
2import { defineStore } from 'pinia'
3
4export const usePostsStore = defineStore('posts', () => {
5  const posts = ref([])
6  const loading = ref(false)
7  const error = ref(null)
8  
9  async function fetchPosts() {
10    loading.value = true
11    error.value = null
12    
13    try {
14      const response = await fetch('/api/posts')
15      if (!response.ok) throw new Error('获取失败')
16      posts.value = await response.json()
17    } catch (err) {
18      error.value = err.message
19    } finally {
20      loading.value = false
21    }
22  }
23  
24  async function createPost(postData) {
25    const response = await fetch('/api/posts', {
26      method: 'POST',
27      body: JSON.stringify(postData)
28    })
29    const newPost = await response.json()
30    posts.value.push(newPost)
31    return newPost
32  }
33  
34  return {
35    posts,
36    loading,
37    error,
38    fetchPosts,
39    createPost
40  }
41})
42

3. 类型安全的 Store(TypeScript)

1// stores/types.ts
2export interface User {
3  id: number
4  name: string
5  email: string
6}
7
8export interface AuthState {
9  user: User | null
10  token: string
11}
12
13// stores/auth.ts
14import { defineStore } from 'pinia'
15import type { User, AuthState } from './types'
16
17export const useAuthStore = defineStore('auth', {
18  state: (): AuthState => ({
19    user: null,
20    token: ''
21  }),
22  
23  getters: {
24    isAuthenticated: (state): boolean => state.user !== null,
25    userName: (state): string => state.user?.name || ''
26  },
27  
28  actions: {
29    setAuth(user: User, token: string): void {
30      this.user = user
31      this.token = token
32    },
33    
34    clearAuth(): void {
35      this.user = null
36      this.token = ''
37    }
38  }
39})
40

4. 测试 Store

1// stores/__tests__/counter.spec.js
2import { setActivePinia, createPinia } from 'pinia'
3import { useCounterStore } from '../counter'
4
5describe('Counter Store', () => {
6  beforeEach(() => {
7    setActivePinia(createPinia())
8  })
9  
10  test('increment', () => {
11    const store = useCounterStore()
12    expect(store.count).toBe(0)
13    
14    store.increment()
15    expect(store.count).toBe(1)
16  })
17  
18  test('doubleCount getter', () => {
19    const store = useCounterStore()
20    store.count = 4
21    expect(store.doubleCount).toBe(8)
22  })
23})
24

总结

什么时候使用哪种状态管理?

场景推荐方案理由
简单组件状态组件内 ref/reactive简单直接
少量组件共享响应式全局对象快速实现
中型应用Pinia (组合式)类型安全,易于测试
大型企业应用Pinia + 严格模式可维护性,团队协作

核心原则

  1. 单一数据源:全局状态集中管理
  2. 状态只读:通过 actions 修改状态
  3. 纯函数修改:相同的输入总是得到相同的输出
  4. 不可变更新:不直接修改原状态,而是创建新状态

Vue3 状态管理完全指南:从响应式 API 到 Pinia》 是转载文章,点击查看原文


相关推荐


Python快速落地的临床知识问答与检索项目(2025年9月教学配置部分)
Allen_Lyb2025/10/17

项目概述与技术选型 本项目定位为临床辅助决策支持工具,而非替代临床诊断的独立系统,旨在解决医疗行业两大核心痛点:一是医学知识更新速率加快,2025 年临床指南年均更新量较 2020 年增长 47%,传统知识管理方式难以同步;二是科室规范呈现碎片化分布,不同院区、亚专科的诊疗流程存在差异,导致知识检索效率低下。技术路线采用 RAG 知识库 + ChatFlow 多轮对话 + 工具节点对接 的三层架构,通过整合指南文献、临床路径和院内 SOP 文档,满足门诊快速问诊、病房随访问答及科室知识库精


滴滴P0级故障背后:互联网公司是如何分级处理线上事故的?
G探险者2025/10/16

大家好,我是G探险者! 像滴滴、阿里、腾讯、华为、字节等大型互联网公司都会对线上故障(事故)进行分级管理,以便快速响应、统一调度、追责复盘。 下面我给你系统性地介绍一下——常见的互联网公司故障分级标准(P0~P4),并结合滴滴、阿里等企业的实践来说明: 🚨 一、故障分级的总体目标 通过分级来 量化故障影响范围与严重程度,从而决定响应等级、通知机制、处理时限与复盘流程。 一般采用的分级体系如下:


面试真实经历某商银行大厂Java问题和答案总结(五)
360_go_php2025/10/15

​ Java 面试常见问题解析​编辑 Java 中的多线程和类加载机制是开发中非常重要的部分。在面试过程中,涉及线程管理和 JVM 的相关问题经常出现。本文将探讨一些 Java 面试中的常见问题,并为每个问题提供详细的解答。 ​编辑--- 1. 线程的同步方法 线程的同步方法是指通过某些机制来控制多线程环境中多个线程对共享资源的访问,以防止数据冲突。以下是几种常见的同步方法: synchronized 关键字: synchronized 关键字可以用来修饰方法或代码块,确保同一时刻只有一个线


Agent 开发设计模式(Agentic Design Patterns )第 2 章:路由模式
shiter2025/10/13

文章大纲 路由模式概览 路由的基本流程 路由机制的类型 路由的部署节点 框架支持 实际应用与用例 1. 人机交互系统 2. 自动化数据处理流水线 3. 多工具/多代理协同系统 4. 系统价值总结 动手代码示例(LangChain) 动手代码示例(Google ADK) 核心总结与最佳实践 什么是


LLM模型与ML算法之间的关系
Juchecar2025/10/12

三要素 机器学习的是算法、算力和数据,LLM的是模型、算力和数据。 ——约去同类项,那么问题来了:算法和模型之间是什么关系? 这是一个非常敏锐且深刻的观察。您提出的论述没有根本性的逻辑矛盾,但揭示了在不同技术范式下,对核心要素的表述侧重点发生了转移。 我们可以从两个层面来理解这个问题: “算法”和“模型”在概念上是相通的,但存在层次和侧重点的差异。 LLM是机器学习的一个子集(尽管是极其庞大和重要的一个),其要素的表述反映了其技术范式的特殊性。 下面我们来详细拆解。 1. “算法”与“模型”


【腾讯拥抱开源】Youtu-Embedding:基于CoDiEmb的一个协作而独特的框架,用于信息检索与语义文本相似性中的统一表征学习
吴脑的键客2025/10/10

🎯 简介 Youtu-Embedding 是由腾讯优图实验室开发的尖端通用文本嵌入模型。该模型在信息检索(IR)、语义文本相似度(STS)、聚类、重排序和分类等各类自然语言处理任务中均展现出卓越性能。 顶尖性能表现:截至2025年9月,在权威的CMTEB(中文大规模文本嵌入基准)评测中以77.46分位列榜首,彰显其强大稳健的文本表征能力。 创新训练框架:采用协同判别式微调框架,通过统一数据格式、任务差异化损失函数及动态单任务采样机制,有效解决多任务学习中的"负迁移"问题。 注:您可


sensitive-word:一个简单易用的敏感词过滤框架
勇哥Java实战2025/10/9

这篇文章,分享一个开源项目:sensitive-word 。 Github 地址:github.com/houbb/sensi… sensitive-word 是一个功能强大的 Java 敏感词过滤框架,它不仅提供了基础的敏感词检测功能,还支持单词标签分类分级、繁简体互换、全角半角互换、汉字转拼音、模糊搜索等高级特性。 它的核心特性如下: 🚀 高性能: 基于 DFA 算法,匹配效率极高 🏷️ 标签分类: 支持敏感词分类分级管理 🔄 字符处理: 支持繁简体、全角半角互换 🎯 模糊搜


Less resolver error:‘~antd/es/style/themes/index.less‘ wasn‘t found.
北阳AI知行录2025/10/7

记录一次使用Ant Design Pro框架时出现的bug 这是我最开始的package.json版本,然后执行npm run build(max build) 打包时会报上面的错误 { "name": "ant-design-pro", "version": "6.0.0", "private": true, "description": "An out-of-box UI solution for enterprise applications", "repo


【微服务】SpringBoot + Docker 实现微服务容器多节点负载均衡详解
小码农叔叔2025/10/6

目录 一、前言 二、前置准备 2.1 基本环境 2.2 准备一个springboot工程 2.2.1 准备几个测试接口 2.3 准备Dockerfile文件 2.4 打包上传到服务器 三、制作微服务镜像与运行服务镜像 3.1 拷贝Dockerfile文件到服务器 3.2 制作服务镜像 3.3 启动镜像服务 3.4 访问一下服务接口 四、配置负载均衡 4.1 源码包方式安装nginx 4.1.1 下载nginx安装包 4.1.2 解压安装包 4.1.3 进入解


为什么 Vue 组件中的 data 必须是一个函数?(含 Vue2/3 对比)
excel2025/10/5

在 Vue 面试或日常开发中,经常会被问到这样一个问题:为什么组件中的 data 必须是一个函数,而根实例的 data 可以是对象或函数? 本文将从 实例与组件的区别、数据污染问题、源码实现原理,以及 Vue2/3 的差异 四个角度进行深入分析。 一、实例与组件定义 data 的区别 在 Vue 根实例 中,data 属性既可以是对象,也可以是函数: // 对象格式 const app = new Vue({ el: "#app", data: { foo: "foo" }

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0