Vue 3 状态管理工具 Pinia

Pinia 简介与官方定位

在掌握 Vue 3 核心语法后,进入项目实战前需要引入状态管理工具。Pinia 是 Vue 官方目前最新推荐的状态管理库,已被正式纳入 Vue 官方生态系统。在 Vue 官方文档中,Pinia 已全面替代 Vuex 成为首选的状态管理方案。虽然 Pinia 与 Vuex 均可在 Vue 3 中进行状态管理,但官方强烈推荐使用 Pinia

Pinia 与 Vuex 的核心优势对比

更简洁的 API 设计

相较于 Vuex,Pinia 提供了更清晰、简单的 API,大幅降低了学习成本。

  • Vuex 的核心概念:包含 statemutationactiongetter 以及用于分模块的 module,概念繁多。在 Vuex 中,异步操作需通过 action 处理,且 action 不能直接修改 state,必须通过 commit 提交 mutation 来修改数据,逻辑较为繁琐。
  • Pinia 的核心概念:仅保留了 state(提供数据)、action(处理业务逻辑)和 getter(基于 state 的计算属性)三个核心概念。
  • 核心改进:Pinia 移除了 mutation。在 Pinia 中,action 既支持异步操作,又可以直接修改 state。这相当于将 Vuex 中的 mutationaction 合二为一,使得代码逻辑更加清晰,页面中只需直接调用 action 中的方法即可完成数据修改。

完美支持组合式 API(Composition API)

为了与 Vue 3 的新语法保持统一,Pinia 提供了符合组合式风格的 API。

  • 选项式 API 的局限性:传统的选项式写法会将所有的 stateactiongetter 分别集中放置。当项目规模扩大或数据量增加时,这种按类型聚合的代码结构会导致维护困难。
  • 组合式 API 的优势:Pinia 支持将数据声明、数据修改方法以及相关的计算属性按业务逻辑进行组合。代码呈现为模块化的代码块,每个代码块包含独立的数据、方法和 getter,从而大幅提升了大型项目的可维护性。在实际开发中,推荐优先使用组合式 API 风格。

摒弃 Modules 概念,实现天然模块化

Pinia 移除了 modules 概念,无需再处理复杂的命名空间(namespaced)配置。

  • 在 Pinia 中,每一个创建的仓库(store默认就是一个独立的模块
  • 各个 store 拥有独立的数据空间,同时天然支持跨模块调用数据。这种设计赋予了状态管理极强的独立性,简化了多模块状态管理的配置过程。

卓越的 TypeScript 支持

相较于 Vuex 对 TypeScript 支持较弱的问题,Pinia 在设计之初就充分考虑了类型推导,提供了极其完善的 TypeScript 支持,能够为开发者提供更好的类型提示和代码检查体验。

Pinia 状态管理工具配置与使用

Pinia 项目初始化策略

Pinia 是 Vue 官方推荐的新一代状态管理工具,用于替代传统的 Vuex。在实际开发中,Pinia 的配置可以在创建 Vue 项目时自动添加。但为了深入理解其底层运行机制,本教程将从零开始,手动创建一个空的 Vue 3 项目,并严格按照官方文档的说明进行安装与配置。

掌握手动配置流程后,在后续的实际项目中,可直接在脚手架初始化阶段勾选 Pinia 选项,由工具自动完成相关配置的初始化。

创建与清理 Vue 3 基础项目

初始化项目

在终端中进入目标目录(如 E 盘根目录),使用 npm 创建最新版本的 Vue 项目。

执行以下命令:

1
npm create vue@latest

根据终端提示,进行如下项目配置选择:

  • Project name:输入项目名称,例如 pinia-demo
  • Add TypeScript:选择 No(本阶段暂不需要)。
  • Add JSX Support:选择 No
  • Add Vue Router:选择 No(暂不添加单页面应用路由)。
  • Add Pinia:选择 No(本次旨在演示手动配置过程)。
  • Add Vitest / Testing Solution:选择 No(暂不需要单元测试)。
  • Add ESLint:选择 Yes(开启代码质量检查)。
  • Add Prettier:根据团队规范选择,本示例中暂不配置。

项目创建完成后,进入项目目录并安装依赖:

1
2
3
cd pinia-demo
npm install
npm run dev

看到终端输出成功提示及本地运行地址,即表示基础项目创建成功。

清理默认模板

通过代码编辑器(如 VS Code)打开项目,对默认生成的模板内容进行清理,以构建纯净的实验环境。

  • 删除 src/assets 目录及其内容。
  • 删除 src/components 目录及其内容。
  • 打开 src/main.js,移除默认引入的样式文件。
  • 打开 src/App.vue,移除默认的模板结构与样式,仅保留最基础的根组件结构。

搭建多组件数据共享演示环境

Pinia 的核心作用是解决多组件共享数据的问题。为了演示该特性,需要在根组件下创建两个子组件,并实现三个组件间的数据联动。

创建子组件

src 目录下新建 components 文件夹,并创建两个子组件文件:Son1.vueSon2.vue

Son1.vue 中编写基础模板:

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
defineOptions({
name: 'Son1Come'
})
</script>

<template>
<div>
<h3>我是子组件 1</h3>
<!-- 预留数据渲染与修改按钮的位置 -->
</div>
</template>

Son2.vue 中编写基础模板:

1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
defineOptions({
name: 'Son2Come'
})
</script>

<template>
<div>
<h3>我是子组件 2</h3>
<!-- 预留数据渲染与修改按钮的位置 -->
</div>
</template>

配置根组件与组件通信需求

src/App.vue 中导入并使用这两个子组件:

1
2
3
4
5
6
7
8
9
10
<script setup>
import Son1 from './components/Son1.vue';
import Son2 from './components/Son2.vue';
</script>

<template>
<h1>我是父组件</h1>
<Son1></Son1>
<Son2></Son2>
</template>

业务需求设定
App.vue 根组件中定义一个共享状态 count(初始值为 0)。Sub1Sub2 不仅需要渲染该数字,还需要提供按钮来修改该数字。点击“加”或“减”按钮时,三个组件中的 count 值必须实现实时联动更新

Pinia 的安装与核心配置

安装 Pinia 依赖

参考 Vue 官方文档或 Pinia 官方文档,使用包管理器安装 Pinia:

1
npm install pinia

配置 Pinia 实例

安装完成后,需要在项目的入口文件 src/main.js 中创建 Pinia 实例,并将其挂载到 Vue 应用上。

Vue 3 环境下的标准配置

在 Vue 3 中,通过 createPinia 创建实例,并使用 app.use() 进行挂载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

// 1. 创建 Vue 应用实例
const app = createApp(App)

// 2. 创建 Pinia 实例
const pinia = createPinia()

// 3. 将 Pinia 实例挂载到 Vue 应用上
app.use(pinia)

// 4. 挂载根组件
app.mount('#app')

知识点强调

  • createPinia:用于生成 Pinia 的核心实例对象。
  • app.use(pinia):将状态管理插件注入到 Vue 实例中,使其在整个组件树中生效。
  • 上述代码支持链式调用(如 createApp(App).use(pinia).mount('#app')),但为了代码的可读性,建议拆分为多行并添加注释。
Vue 2 环境下的处理方案

针对 Vue 2 项目,Pinia 同样提供了完善的支持,但需要额外安装组合式 API 插件,并在配置方式上有所区别。

安装依赖

1
npm install pinia @vue/composition-api

Vue 2 配置代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue'
import { createPinia, PiniaVuePlugin } from 'pinia'
import App from './App.vue'

// 1. 安装 Composition API 插件
Vue.use(PiniaVuePlugin)

// 2. 创建 Pinia 实例
const pinia = createPinia()

// 3. 创建 Vue 2 实例并注入 Pinia
new Vue({
pinia,
render: h => h(App)
}).$mount('#app')

注意:在 Vue 2 中,必须通过 Vue.use(PiniaVuePlugin) 启用组合式 API 支持,并将 pinia 实例作为选项传入 new Vue() 的配置对象中。

总结

本节完成了 Pinia 状态管理工具的前期准备工作,核心内容包括:

  1. 环境搭建:从零创建并清理了一个纯净的 Vue 3 项目,构建了包含一个根组件和两个子组件的演示环境。
  2. 需求明确:确立了多组件共享同一状态(count)并实现双向联动修改的业务需求。
  3. 插件配置:按照官方文档规范,完成了 Pinia 依赖的安装,并在 main.js 中成功创建了 Pinia 实例并完成挂载。同时补充了 Vue 2 环境下的差异化配置方案。

接下来,将深入探讨 Pinia 的核心概念(如 Store 仓库State 状态Getters 计算属性Actions 操作方法),并正式实现多组件数据联动的具体功能。

Pinia 状态管理核心概念

在 Pinia 中,状态管理的核心在于维护独立的状态模块。为了管理特定的数据状态(例如计数器的加减操作),需要创建专门的 Store(仓库)模块。组件通过引入并使用该 Store,即可实现状态的读取与修改。所有独立的 Store 模块最终都会挂载到同一个全局状态树上。

定义 Store 的两种语法风格

在 Pinia 中定义 Store,需要导入 defineStore 函数。该函数接收两个核心参数:

  1. 唯一标识(ID):作为仓库的唯一名称,用于区分不同的 Store 模块。
  2. 配置项:用于定义 Store 的具体内容,支持选项式 API组合式 API 两种风格。

选项式 API (Option Store)

第二个参数传入一个配置对象,包含 stategettersactions 选项:

  • state:通过箭头函数返回一个对象来提供初始数据。
  • getters:提供基于 state 派生的计算属性,第一个参数为 state,用于访问内部数据。
  • actions:提供操作数据的方法。与 Vuex 不同,Pinia 取消了 mutations,将同步与异步操作统一合并至 actions 中。在 actions 内部,通过 this 直接访问和修改 state 中的数据,且原生支持异步操作。

组合式 API (Setup Store)

第二个参数传入一个 Setup 函数,其语法与 Vue 的组合式 API 高度一致:

  • 使用 refreactive 定义 state(状态)
  • 使用 computed 定义 getters(计算属性)
  • 使用普通 function 定义 actions(操作方法)
  • 最终通过 return 将需要暴露的状态和方法返回。

风格选择建议
官方推荐使用组合式 API (Setup Store)。选项式 API 强制将同类选项(如所有 state、所有 actions)集中放置,不利于复杂业务逻辑的维护;而组合式 API 允许将同一业务逻辑的状态、计算属性和操作方法聚合在一起,代码结构更清晰,且与组件内的组合式 API 编码风格保持统一。两者底层实现一致,可根据团队规范灵活选择。

实战:创建与定义 Store

创建 Store 文件

在项目的 src 目录下新建 store 文件夹,并创建具体的仓库文件(如 counter.js)。Pinia 支持创建任意多个子仓库,且各仓库之间相互独立。

编写 Store 代码

counter.js 中引入 defineStore 并定义仓库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// defineStore 返回一个函数,必须将其导出
export const useCounterStore = defineStore('counter', () => {
// 1. 声明 State (状态)
const count = ref(100)
const message = ref('hello pinia')

// 2. 声明 Getters (计算属性)
const double = computed(() => count.value * 2)

// 3. 声明 Actions (操作方法)
const addCount = () => {
count.value++
}

const subCount = () => {
count.value--
}

// 4. 返回暴露的数据与方法
return {
count,
message,
double,
addCount,
subCount
}
})

在组件中使用 Store

引入与获取 Store 实例

在组件中导入对应的 Store 函数并调用,即可获取当前仓库的实例对象:

1
2
3
4
import { useCounterStore } from '@/store/counter'

// 调用函数获取仓库实例
const counterStore = useCounterStore()

访问状态与计算属性

通过仓库实例直接访问暴露的状态和计算属性:

1
2
3
<p>当前计数:{{ counterStore.count }}</p>
<p>提示信息:{{ counterStore.message }}</p>
<p>双倍计数:{{ counterStore.double }}</p>

重要警告禁止对 Store 实例进行解构赋值。直接解构会导致数据丢失响应式特性。必须通过 实例名.属性名 的方式进行访问。

修改状态 (调用 Actions)

页面中不应直接修改 Store 内的状态,而应通过调用实例上的 Actions 方法来修改数据:

1
2
<button @click="counterStore.addCount">增加</button>
<button @click="counterStore.subCount">减少</button>

Actions 方法内部不仅支持同步操作,也完全支持异步操作(如发送网络请求后修改状态)。

跨组件状态共享

Pinia 的核心优势在于跨组件状态共享。在任意组件(如组件 A、组件 B)中,只需重复上述引入和调用 useCounterStore() 的步骤,即可获取同一个 Store 实例,实现状态的实时同步与共享。

Pinia 与 Vuex 的核心差异总结

相较于传统的 Vuex,Pinia 在架构设计和 API 调用上进行了大幅简化:

  1. 移除 Mutations:不再区分同步的 Mutations 和异步的 Actions,所有数据修改逻辑统一在 Actions 中处理。
  2. 简化调用方式:在组件中调用 Actions 时,直接通过 实例名.方法名() 调用,无需记忆并使用 commitdispatch 等辅助触发函数。
  3. 完美的 TypeScript 支持:组合式 API 的写法使得类型推导更加自然和准确。
  4. 扁平化模块设计:每个 Store 都是独立的扁平化模块,避免了 Vuex 中复杂的嵌套模块与命名空间问题。

Pinia 异步 Action 的实现与数据渲染

异步 Action

在 Pinia 状态管理中,Action 不仅支持同步操作,同样原生支持异步操作

  • 在 Action 函数内部,无需刻意区分同步或异步逻辑。
  • 可以直接修改状态数据,也可以先发送异步请求,在获取数据后再更新状态。
  • 其编写方式与在组件中获取异步数据的逻辑完全一致。

需求与接口说明

业务需求

在 Pinia 中获取频道列表数据,并将获取到的数据渲染到 App 组件的模板中。

接口数据结构

接口用于获取新闻类频道列表(如财经、体育等)。返回的数据结构如下:

  • 根节点包含 data 对象。
  • data 中包含 channels 数组。
  • channels 数组中的每个对象包含 idname 属性。

环境准备

由于需要发送网络请求,需在项目中安装 axios 库。
在终端执行以下命令进行安装:

1
npm add axios

安装完成后,重新启动项目以使依赖生效。

创建与配置 Store 模块

新建 Store 文件

store 目录下新建 channel.js 文件,用于管理频道相关状态。

定义 Store 模块

使用 defineStore 定义仓库,并遵循 Pinia 的命名规范(以 use 开头,Store 结尾,中间为仓库名)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { defineStore } from 'pinia'
import { ref } from 'vue'
import axios from 'axios'

// 导出 useChannelStore 方法
export const useChannelStore = defineStore('channel', () => {
// 1. 声明数据
// 使用 ref 定义响应式数据,初始值为空数组
const channelList = ref([])

// 2. 声明操作数据的方法(支持异步)
const getList = async () => {
// 发送 GET 请求获取频道数据
const res = await axios.get('接口地址')

// 解构获取返回数据中的 channels 数组
// axios 返回的 res 包含 data 属性,后台返回的数据结构中又包含一层 data
const { data: { channels } } = res.data

// 将获取到的数据赋值给 channelList
channelList.value = channels

// 打印数据以验证请求结果
console.log(channels)
}

// 3. 导出数据和操作方法
return {
channelList,
getList
}
})

核心知识点解析

  • defineStore:用于定义 Pinia 仓库,第一个参数为仓库名称,第二个参数为 Setup 函数。
  • ref:用于声明响应式状态数据。
  • async/await:在 Action 中直接使用异步函数处理网络请求。
  • 数据解构:根据 axios 和后端接口的数据包装层级,正确解构出目标数据。

在组件中调用与渲染数据

在 Vue 2 环境中(Vue 2.7+ 或配合 @vue/composition-api 插件),可通过组件的 setup 选项引入并调用 Store。

组件逻辑编写

App.vue 中引入 useChannelStore,并配置点击事件与数据渲染逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { useChannelStore } from '@/store/channel'

export default {
setup() {
// 实例化 Store
const channelStore = useChannelStore()

// 定义点击事件处理函数
const handleGetChannels = () => {
channelStore.getList()
}

// 返回模板所需的数据和方法
return {
channelStore,
handleGetChannels
}
}
}

模板渲染编写

在模板中使用 v-for 指令遍历 channelList,并绑定点击事件触发异步请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
<hr />
<!-- 绑定点击事件,触发异步 Action -->
<button @click="handleGetChannels">获取频道数据</button>

<!-- 渲染频道列表 -->
<ul>
<li
v-for="item in channelStore.channelList"
:key="item.id"
>
{{ item.name }}
</li>
</ul>
</div>
</template>

核心知识点解析

  • Store 实例化:在 setup 函数中调用 useChannelStore() 获取 Store 实例。
  • 状态访问:在模板中直接通过 channelStore.channelList 访问响应式数据,无需额外解构。
  • v-for 渲染:使用 v-for 遍历数组时,必须绑定唯一的 :key 属性(此处使用 item.id)。

总结与验证

运行验证

  • 启动项目并打开浏览器开发者工具。
  • 点击“获取频道数据”按钮,观察控制台(Console)是否打印出对应的频道数据。
  • 检查 Pinia 开发者工具(Devtools),确认 channel 仓库中的 channelList 状态已更新。
  • 确认页面成功渲染出对应的频道名称列表。

核心总结

  • Pinia 的 Action 天然支持异步操作,只需在方法内部正常编写异步请求逻辑即可。
  • 相比于 Vuex,Pinia 在处理异步操作时无需区分 mutationsactions,代码结构更加扁平、简洁。
  • 将网络请求封装在 Store 的 Action 中,有利于实现业务逻辑与视图组件的解耦。

Vue 状态管理:Store 解构与响应式原理

问题引入:Store 解构导致响应式丢失

在实际开发中,直接对 Store 对象进行解构以获取所需状态时,会引发响应式丢失的问题。

问题现象演示

假设存在一个 counterStore,从中直接解构出 countmessage 状态:

1
2
const counterStore = useCounterStore();
const { count, message } = counterStore;

在页面初始渲染时,数据能够正常显示。但当触发状态更新操作(如点击增加按钮)时,Store 内部的底层数据已发生改变,而页面中通过解构获取的 countmessage 并未同步更新。此现象表明,直接解构 Store 对象会导致状态丢失响应式特性

解决方案:引入 storeToRefs

为了解决解构导致的响应式丢失问题,Pinia 提供了 storeToRefs 方法。在解构 Store 时,使用该方法包裹 Store 实例,即可保持状态的响应式:

1
2
3
4
import { storeToRefs } from 'pinia';

const counterStore = useCounterStore();
const { count, message } = storeToRefs(counterStore);

经过 storeToRefs 处理后,再次触发状态更新,页面数据能够正常响应并同步更新。

官方文档解析与底层原理

Store 的懒加载与模块化管理

根据官方文档说明,在调用 useStore 之前,Store 实例不会被创建。这意味着即使在项目中定义了大量的 Store 模块,只要某个 Store 未被任何组件导入和使用,该 Store 就不会被初始化。这种懒加载机制有助于优化应用性能。

此外,官方建议在不同的文件中定义各个 Store。将状态、计算属性和操作按业务模块分门别类地存放在独立文件中,能够显著提升代码的可维护性。

Store 的底层包装与 Proxy 劫持

官方文档明确指出:Store 是一个使用 reactive 包装的对象。因此,在访问 Store 内部的属性时,无需像使用 ref 那样添加 .value 后缀。

然而,文档同时强调不能直接解构 Store 对象,因为这会破坏其响应性。其底层原理在于:reactive 的响应式实现依赖于 ES6 的 Proxy 代理机制Proxy 是针对整个对象及其嵌套属性进行拦截和监听的。

解构破坏响应式的根本原因

当对 Store 对象进行解构时(例如 const { name, doubleCount } = store),实际上是声明了新的独立变量,并将 Store 对象中对应属性的当前值直接赋给了这些变量。

这一赋值操作导致新变量与原始的响应式对象完全脱离了关联,相当于进行了一次基本数据类型的值拷贝。因此,后续对 Store 内部状态的修改,无法触发这些独立变量的更新,从而导致响应式失效。

storeToRefs 实践

状态与方法的解构规范

为了从 Store 中提取属性并保持响应式,必须使用 storeToRefs 方法。该方法会为每一个响应式属性创建一个 ref 引用,从而确保解构后的变量依然与 Store 保持响应式连接。

在实际开发中,Store 通常包含状态(State/Getters)和动作(Actions)。针对这两类属性,解构的最佳实践如下:

  • 解构状态(属性):必须使用 storeToRefs 进行包裹,以维持数据的响应式。
  • 解构动作(方法)直接解构即可。因为方法本身是固定的函数,不需要响应式处理,直接解构不会影响其正常调用。
1
2
3
4
5
const store = useCounterStore();

// 正确做法:状态使用 storeToRefs,方法直接解构
const { count, message } = storeToRefs(store);
const { increment, fetchData } = store;

直接访问与解构访问的场景选择

除了使用解构语法,开发者也可以始终通过 store.propertyName 的方式直接访问状态和方法。

  • 直接访问(store.xxx:语法统一,无需引入额外方法,适用于 Store 内部属性和方法较少的场景。
  • 解构访问:当 Store 内部定义了海量的状态和方法时,频繁使用 store.xxx 会导致代码冗长。此时采用解构语法可以简化代码结构,提升可读性。

开发者应根据实际业务场景和代码复杂度,灵活选择最合适的访问方式。

实战演练:综合应用 storeToRefs

以下通过一个具体的业务场景,演示如何结合使用直接解构与 storeToRefs。假设当前组件需要使用 channelStore 中的 channelList(状态)和 getList(动作):

1
2
3
4
5
6
7
8
9
10
11
12
13
import { storeToRefs } from 'pinia';
import { useChannelStore } from '@/stores/channel';

const channelStore = useChannelStore();

// 1. 解构动作(方法):直接解构
const { getList } = channelStore;

// 2. 解构状态(属性):使用 storeToRefs 保持响应式
const { channelList } = storeToRefs(channelStore);

// 3. 调用方法获取数据
getList();

通过上述改造,组件能够成功发送异步请求,并将获取到的数据正确渲染至页面。同时,由于 channelList 经过了 storeToRefs 处理,后续若该状态发生变更,页面视图也会自动进行响应式更新。

store 解构总结

在 Vue 状态管理中,处理 Store 数据解构时需严格遵循响应式规则。直接解构 Store 对象会破坏 Proxy 代理,导致响应式丢失。通过引入并合理使用 storeToRefs,可以有效提取状态并维持响应式连接。同时,需明确区分状态与动作的解构方式,结合项目实际情况选择最优的代码组织形式,以编写出严谨、高效且易于维护的前端代码。

Pinia 状态管理与数据持久化教程

Pinia 调试工具的使用

在开发过程中,可以通过 Vue Devtools 浏览器插件对 Pinia 进行调试。

  • 打开浏览器开发者工具,切换至 Vue Devtools 面板。
  • 选择 Pinia 选项卡,即可实时查看和调试 Store 中的状态数据。
  • 此调试方式与 Vue 2 时代调试 Vuex 的体验基本一致,能够直观地追踪状态变化。

二、 Pinia 数据持久化插件简介

在传统的 Vue 2 与 Vuex 开发中,实现数据本地持久化通常需要手动封装 localStorage.getItemlocalStorage.setItem 方法。为了提升开发效率,Pinia 提供了官方的持久化插件 pinia-plugin-persistedstate

  • 该插件能够自动处理状态数据的本地存储与读取。
  • 其 API 设计与 Vuex 生态中的 vuex-persistedstate 高度相似。掌握该插件后,也可将其思路平移至 Vue 2 的 Vuex 项目中。
  • 在实际企业级开发中,使用成熟插件替代手动封装是标准规范。

持久化插件的安装与基础配置

安装插件

使用 npm 或其他包管理器安装持久化插件:

1
npm i pinia-plugin-persistedstate

注:请确保当前项目使用的 Pinia 版本高于 2.0.0。

在 main.js 中注册插件

持久化插件需要挂载到 Pinia 实例上,而不是直接挂载到 Vue App 实例上。

1
2
3
4
5
6
7
8
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
// 将持久化插件注册到 Pinia 实例
pinia.use(piniaPluginPersistedstate)

app.use(pinia)

在 Store 模块中开启持久化

在需要持久化的 Store 模块中,添加 persist 配置项。

组合式 API (Setup 语法):
defineStore 的第三个参数(配置对象)中添加 persist: true

1
2
3
4
5
6
7
8
9
10
11
12
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const message = ref('Hello Pinia')

return { count, message }
}, {
// 开启当前模块的持久化
persist: true
})

选项式 API (Options 语法):
在与 stategettersactions 同级的位置添加 persist: true

1
2
3
4
5
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, message: 'Hello Pinia' }),
// 开启当前模块的持久化
persist: true
})

配置完成后,刷新页面时,插件会自动优先从本地存储中读取数据,并在数据变更时自动同步至本地存储。

持久化插件的高级配置

更多配置可以查看官网配置

默认情况下,插件使用 localStorage 进行存储,以 Store 的 id 作为存储的 key,并对整个 state 进行 JSON.stringify 序列化与 JSON.parse 反序列化。如需自定义,可将 persist 配置为一个对象。

自定义存储键名 (key)

修改本地存储中的键名,避免多项目或模块间的命名冲突。

1
2
3
persist: {
key: 'custom-counter-key'
}

更改存储方式 (storage)

默认使用 localStorage。若需更改为 sessionStorage,可直接赋值。

1
2
3
persist: {
storage: sessionStorage
}

注:传入的对象必须具备 getItemsetItem 方法。

指定持久化状态 (paths)

默认情况下,整个 state 都会被持久化。若只需持久化部分数据,可通过 paths 数组指定具体的属性名,支持嵌套属性(如 'user.name')。

1
2
3
4
persist: {
// 仅持久化 count 属性,message 属性不会被持久化
paths: ['count']
}

Pinia 核心知识点总结

  1. 核心定位Pinia 是 Vue 官方推荐的新一代状态管理工具,旨在替代 Vue 2 时代的 Vuex,同时完美兼容 Vue 2 与 Vue 3。
  2. 移除 Mutations:Pinia 彻底移除了 mutations 概念。所有的状态修改逻辑统一在 actions 中处理,且 actions 同时支持同步与异步操作。
  3. Getters 的实现:在组合式 API (Setup) 中,getters 直接通过 Vue 的 computed 计算属性来实现。
  4. 保持响应式解构:从 Pinia Store 中解构赋值时,为保持数据的响应式特性,必须使用 storeToRefs 函数进行包裹。
  5. 数据持久化:通过引入并配置 pinia-plugin-persistedstate 插件,可快速、优雅地实现状态数据的本地持久化,无需手动操作 Web Storage API。