Pinia 状态管理

Pinia 状态管理

一、为什么要讲 Pinia

原因:

  • Vue3 官方推荐的状态管理方案

  • 用来替代 Vuex(Vuex 5 已停止推进)

  • 更符合 Vue3 中 Composition API 的设计理念

  • 比起 Vuex 使用更加方便

vuex 与 pinia 对比

特性 Vuex (3/4) Pinia
核心组成 State, Getters, Mutations, Actions State, Getters, Actions
Mutations 必须通过它修改 State (繁琐) 废弃,Actions 即可修改 State
TypeScript 支持较弱,需大量额外定义 原生完美支持,类型推断极佳
模块化 单一 Store 树,需嵌套 Modules 多 Store 设计,扁平化结构,自动拆包
体积 约 10kb 约 1kb (极度轻量)

二、什么是状态管理(State Management)

什么是「状态」?

在前端应用中,状态(state)指的是:

  • 状态 = 程序运行时"留在内存里、会变化的数据"

  • 状态的核心特征

特征 说明 例子
存在于内存中 页面刷新即消失(除非持久化) 用户登录后的 token
会随时间变化 用户操作、API 响应会改变它 购物车商品数量增减
影响 UI 渲染 状态变 → 视图自动更新 切换主题色后界面变色
需要被追踪 框架通过响应式系统监控变化 Vue 的 ref/reactive

不使用状态管理会遇到什么问题?

当项目变大后,会出现:

  • 父子组件层层传 props

  • 兄弟组件通信复杂

  • 数据分散在各个组件,难以维护

  • 同一份数据被多处复制,容易不一致

为什么需要状态管理?

状态管理就是把这些状态从"散落在各个组件里"抽出来,集中存、集中改、集中通知 ,让任何组件都能同一份数据源、统一规则地读取和更新,避免:

  • 父→子→孙,一层层 props 传递

  • 事件一层层 emit 回去

  • 不同组件各自 copy 一份数据,改完互相不一致

总结

状态 = 应用运行时需要被框架追踪、会影响视图、且可能被多个组件共享的"活"数据

(而状态管理 = 让这些"活"数据变得有序、可控、可预测的工具)


三、Pinia 在项目中的定位

plaintext 复制代码
Vue App
 ├─ Components(组件)
 ├─ Router(路由)
 ├─ Pinia(全局状态)  ← 负责跨组件数据共享
 └─ Services / API

四、Pinia 的核心概念

Store 是什么

Store 是保存状态 ( state ) 和业务逻辑的实体,它并不与组件树绑定。

换句话说,它存储着全局的状态数据。

一个 Store 由哪几部分组成

部分 作用
state 定义状态数据
getters 类似计算属性(派生状态)
actions 业务逻辑 / 异步操作

总结类比

  • 可以把 pinia 类比为一个一个中心化的"仓库"。就像一个公司有一个中央档案室,不管哪个 部门(Vue 组件 )需要 修改 某个 文件(数据) ,++都得去这个档案室拿取++。

  • 解释:

    • 各部门:各个 Vue 组件

    • 文件:State 数据

    • 看:组件调用 Store 里面 State 状态数据

    • 修改:Store 里面修改 State 状态数据的方法(Actions)

    • 都得去这个档案室拿取

      • 我们如果想要在 Vue 组件里面使用 Store 仓库里面的数据与方法,肯定要先在组件里引入使用这个 Store 仓库

五、Pinia 的基础使用流程

安装 Pinia

bash 复制代码
npm install pinia

在 main.ts 中注册 Pinia

ts 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

定义一个 Store

ts 复制代码
import { defineStore } from 'pinia'

// 'use' 是约定俗成的前缀,便于识别这是一个 store
// 'user' 是 store 的唯一 ID
export const useUserStore = defineStore('user', {
  // Store 配置项
  // state、getter 和 action 都写这里面
  // 这里面包含了两种写法,我们会在下面介绍
}) 

在组件中使用 Store

ts 复制代码
// 1. 导入
import { useUserStore } from '@/stores/user'

// 2. 调用
const userStore = useUserStore()

// 3. 获取调用结果里面的数据方法
userStore.setToken('abc')
console.log(userStore.isLogin)

六、Pinia 的两种写法

1. Options Store(类似 Vuex,适合新手)

ts 复制代码
// 选项式写法
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: '',
    userInfo: null
  }),
  getters: {
    isLogin: (state) => !!state.token
  },
  actions: {
    setToken(token: string) {
      this.token = token
    }
  }
}) 

2. Setup Store(推荐写法)

ts 复制代码
// 组合式写法(推荐)
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useMainStore = defineStore('main', () => {
  // 1. 定义状态 (State) - 使用 ref 或 reactive
  const count = ref(0)
  const name = ref('Eduardo')
  const user = ref({ name: 'John', age: 30 })

  // 2. 定义计算属性 (Getters) - 使用 computed
  const doubleCount = computed(() => count.value * 2)
  const isEven = computed(() => count.value % 2 === 0)

  // 3. 定义动作 (Actions) - 定义函数
  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  // 可以包含异步操作
  async function fetchUserAge() {
    try {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 1000))
      user.value.age += 1
    } catch (error) {
      console.error('Failed to fetch user age:', error)
    }
  }

  // 4. 必须返回你想要暴露给外界的部分
  return {
    // State
    count,
    name,
    user,

    // Getters
    doubleCount,
    isEven,

    // Actions
    increment,
    decrement,
    fetchUserAge
  }
})

Setup Store 与 Composition API 思想完全一致

七、Pinia 中的数据更新原则

1. 可以直接修改 state

ts 复制代码
// 可以在引入 Store 仓库的组件里面直接更改
// 只是通常不建议这么做

store.count++
为什么不推荐:
  • 虽然可以直改,但在中大型项目中,如果随处直接修改 Store 数据,会带来如下一些问题:

    • 业务逻辑碎片化

      • 如果修改数据的逻辑(比如:修改价格前要检查库存、计算折扣、验证权限)散落在 10 个不同的 .vue 文件里。

      • 后果 :当你需要修改这个逻辑时,你得满项目找这 10 个地方。如果写在 action 里,你只需要改 Store 里的一个函数

    • 调试与追踪困难

      • Pinia 的 Action 像是一个"带名字的事务所"

      • 直接修改:在 DevTools 里,你可能只看到数据变了,但不知道是哪个组件、因为什么逻辑触发的

      • Action 修改 :DevTools 会记录下 Action 的名称和参数,你可以清晰地看到"用户点击了支付按钮 -> 触发了 checkout Action -> 状态变更"

2. 推荐在 actions 中集中修改

优点:

  • 逻辑集中

  • 方便调试

  • 易于维护


八、Pinia 数据持久化

  • 介绍:

    • Pinia 数据持久化是指将 Store 中的数据自动保存到浏览器的本地存储

      • localStorage

      • sessionStorage

    • 这样即使用户刷新页面或关闭浏览器后再次打开,数据依然能够恢复

    • 对于记住用户的登录状态、购物车内容、主题偏好等场景非常有用

  • Pinia 官方并没有内置持久化插件,但社区提供了一个非常流行且好用的官方推荐插件

    • pinia-plugin-persistedstate
  • pinia-plugin-persistedstate的使用方法:

    • 安装插件:首先,我们需要在我们的项目里面安装 pinia-plugin-persistedstate 插件
    powershell 复制代码
    npm install pinia-plugin-persistedstate
    # 或者
    yarn add pinia-plugin-persistedstate
    # 或者
    pnpm add pinia-plugin-persistedstate
    • 在 Pinia 实例中注册插件

      javascript 复制代码
      // main.js 或 store/index.js
      import { createApp } from 'vue';
      import { createPinia } from 'pinia';
      import persistedstate from 'pinia-plugin-persistedstate'; // 导入插件
      
      const app = createApp(App);
      
      const pinia = createPinia();
      pinia.use(persistedstate); // 注册插件
      
      app.use(pinia);
      app.mount('#app');
    • 在 Store 定义中启用持久化

      • 在使用 defineStore 定义 Store 时,通过添加 persist: true 或一个配置对象来启用持久化

        javascript 复制代码
        // 示例1:简单的配置
        // stores/counter.js
        import { defineStore } from 'pinia';
        
        export const useCounterStore = defineStore('counter', () => {
          // Store 配置项
          // state、getter 和 action 都写这里面
          , {
          // 在这里添加持久化配置
          persist: true // 最简单的形式,会持久化整个 Store 的 state
        });
        
         // ================================================================
          
        // 示例 2:更详细的配置
        export const useUserStore = defineStore('user', () => {
          // Store 配置项
        }, {
          persist: {
            // key: 自定义存储的键名,默认为 storeId
            key: 'my_user_store_key',
        
            // storage: 选择存储方式,默认为 localStorage
            storage: localStorage, // 或 sessionStorage
        
            // paths: 指定需要持久化的 state 字段(数组)
            // 如果不设置,则默认持久化整个 state
            paths: ['profile', 'preferences'], // 只持久化 profile 和 preferences
        
            // 可选:自定义序列化函数 (serializer)
            serializer: {
              serialize: JSON.stringify,
              deserialize: JSON.parse,
            },
        
            // 可选:自定义存储逻辑 (advanced)
            // 如需要对存储的数据进行加密/解密等操作
          }
        });
      • 配置项详解:

        • persist: true:

          • 最简单的配置,会将整个 Store 的 state(即 return 出来的 refreactive 对象)完整地保存到 localStorage 中。
        • persist: { ... }:

          • key (string): 指定在浏览器存储中使用的键名。如果不指定,默认使用 defineStore 时的第一个参数(即 storeId)。

          • storage (Storage): 指定使用的 Web Storage 接口。可以是 localStorage(数据永久保存,除非手动清除)或 sessionStorage(数据仅在当前会话期间有效,关闭浏览器标签页/窗口后消失)。默认为 localStorage

          • paths (Array<string>): 一个字符串数组,用于指定哪些 state 属性需要被持久化。如果设置了 paths,则只有列出的属性会被保存,其他属性会被忽略。这对于不想持久化临时数据或敏感信息(如 token)非常有用。

          • serializer (object): 允许你自定义数据的序列化和反序列化方式。默认使用 JSON.stringifyJSON.parse。在某些特殊情况下可能需要自定义。

相关推荐
ZC跨境爬虫2 小时前
跟着 MDN 学CSS day_39:(Flexbox 弹性盒子核心机制)
前端·css·ui·html·tensorflow
小陈同学呦2 小时前
前端如何处理订单状态导航的数据竞态问题
前端·javascript
喵个咪2 小时前
GoWind Toolkit 前端代码生成|Vue3(ElementPlus/Vben)、React(AntDesign)全自动一键生成教程
前端·vue.js·react.js
isyangli_blog2 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008112 小时前
FastAPI APIRouter
开发语言·python
Benszen2 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆2 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木2 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充3 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~3 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言