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。在某些特殊情况下可能需要自定义。

相关推荐
mCell6 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell7 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭7 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
寻寻觅觅☆7 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
少云清7 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
萧曵 丶8 小时前
Vue 中父子组件之间最常用的业务交互场景
javascript·vue.js·交互
银烛木8 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076608 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声8 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易8 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari