Vue 3 入门教程7 - 状态管理工具 Pinia

一、Pinia 简介

Pinia 是 Vue 官方推荐的状态管理工具,是 Vuex 的继任者。它简化了状态管理的流程,提供了更简洁的 API,同时对 TypeScript 有更好的支持,并且与 Vue 3 的组合式 API 完美契合。

1.1 Pinia 的优势

  • 简洁的 API:相比 Vuex,Pinia 减少了诸如 mutations 等概念,使代码结构更简洁,学习成本更低。
  • 完整的 TypeScript 支持:天生支持 TypeScript,无需额外配置即可获得类型推断,提升开发体验和代码健壮性。
  • 与组合式 API 兼容:可以在 Pinia 中直接使用组合式 API 的特性,如 ref、reactive 等,便于逻辑复用。
  • 无需嵌套模块:Pinia 中的 Store 是扁平化的,无需像 Vuex 那样嵌套模块,简化了状态的组织和访问。
  • 开发工具支持:与 Vue DevTools 深度集成,支持时间旅行调试等功能。

二、Pinia 的安装与配置

2.1 安装 Pinia

在 Vue 3 项目中,通过 npm 或 yarn 安装 Pinia:

复制代码
# 使用 npm

npm install pinia

# 使用 yarn

yarn add pinia

2.2 初始化 Pinia

在项目入口文件(通常是 main.js)中创建 Pinia 实例并挂载到 Vue 应用上:

复制代码
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
// 创建 Pinia 实例
const pinia = createPinia()
// 创建 Vue 应用
const app = createApp(App)
// 挂载 Pinia 实例到应用
app.use(pinia)
// 挂载应用到 DOM
app.mount('#app')

完成以上步骤后,Pinia 就已成功集成到 Vue 项目中,可以开始创建和使用 Store 了。

三、创建和使用 Store

Store 是 Pinia 的核心概念,用于存储应用的状态和相关逻辑。每个 Store 都是一个独立的模块,负责管理一部分状态。

3.1 定义 Store

使用 defineStore 函数定义一个 Store,该函数接收两个参数:Store 的唯一标识(id)和一个配置对象(包含 state、getters、actions 等)。

示例:创建一个计数器 Store
html 复制代码
// stores/counter.js

import { defineStore } from 'pinia'

// 定义并导出 Store,第一个参数是 Store 的唯一 id

export const useCounterStore = defineStore('counter', {

// 状态:存储应用的响应式数据

state: () => ({

count: 0,

message: 'Hello Pinia'

}),

// 计算属性:基于 state 派生的状态,具有缓存特性

getters: {

// 基本使用:接收 state 作为参数

doubleCount: (state) => state.count * 2,

// 使用 this 访问其他 getters(此时需要指定返回值类型)

doubleCountPlusOne() {

// this 指向当前 Store 实例

return this.doubleCount + 1

}

},

// 方法:用于修改状态或执行异步操作

actions: {

// 同步操作

increment() {

this.count++

},

decrement() {

this.count--

},

// 接收参数

setCount(value) {

this.count = value

},

// 异步操作(如发送请求)

async fetchData() {

// 模拟异步请求

const response = await new Promise(resolve => {

setTimeout(() => {

resolve({ message: '从服务器获取的数据' })

}, 1000)

})

this.message = response.message

}

}

})

3.2 在组件中使用 Store

在组件中,通过导入定义好的 Store 函数并调用,即可获取 Store 实例,进而访问和修改状态。

组件示例
html 复制代码
<template>

<div>

<p>count: {{ counterStore.count }}</p>

<p>message: {{ counterStore.message }}</p>

<p>doubleCount: {{ counterStore.doubleCount }}</p>

<p>doubleCountPlusOne: {{ counterStore.doubleCountPlusOne }}</p>

<button @click="counterStore.increment">+1</button>

<button @click="counterStore.decrement">-1</button>

<button @click="counterStore.setCount(10)">设置为 10</button>

<button @click="counterStore.fetchData">获取异步数据</button>

</div>

</template>

<script setup>

// 导入 Store 函数

import { useCounterStore } from './stores/counter'

// 获取 Store 实例

const counterStore = useCounterStore()

</script>

在组件中使用 Store 的步骤:

  1. 导入定义好的 useXXXStore 函数。
  1. 调用该函数获取 Store 实例(注意:同一个 Store 函数多次调用返回的是同一个实例)。
  1. 通过实例访问 state 中的属性、getters 中的计算属性,或调用 actions 中的方法。

四、状态的修改与重置

4.1 修改状态

在 Pinia 中,修改状态的方式非常灵活:

  • 直接修改状态(适用于简单场景)。
  • 通过 actions 中的方法修改(适用于复杂逻辑或需要复用的操作)。
直接修改状态示例
html 复制代码
<template>

<div>

<p>count: {{ counterStore.count }}</p>

<button @click="directlyModify">直接修改 count</button>

</div>

</template>

<script setup>

import { useCounterStore } from './stores/counter'

const counterStore = useCounterStore()

const directlyModify = () => {

// 直接修改状态(Pinia 允许这种操作)

counterStore.count = 100

}

</script>

虽然 Pinia 允许直接修改状态,但在实际开发中,对于复杂的状态修改逻辑,建议封装到 actions 中,以保证代码的可维护性和可测试性。

4.2 重置状态

使用 Store 实例的 $reset 方法,可以将状态重置为初始值。

html 复制代码
<template>

<div>

<p>count: {{ counterStore.count }}</p>

<p>message: {{ counterStore.message }}</p>

<button @click="counterStore.increment">+1</button>

<button @click="counterStore.fetchData">获取数据</button>

<button @click="resetState">重置状态</button>

</div>

</template>

<script setup>

import { useCounterStore } from './stores/counter'

const counterStore = useCounterStore()

const resetState = () => {

// 重置状态为初始值

counterStore.$reset()

}

</script>

五、Store 的组合与依赖

在大型应用中,通常会创建多个 Store 来管理不同模块的状态。Pinia 支持在一个 Store 中使用另一个 Store,实现状态的组合与依赖。

5.1 跨 Store 访问示例

html 复制代码
// stores/user.js

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {

state: () => ({

name: '张三',

age: 20

}),

actions: {

setName(name) {

this.name = name

}

}

})
html 复制代码
// stores/profile.js

import { defineStore } from 'pinia'

import { useUserStore } from './user'

export const useProfileStore = defineStore('profile', {

getters: {

userInfo() {

// 访问 user Store 中的状态

const userStore = useUserStore()

return `姓名:${userStore.name},年龄:${userStore.age}`

}

},

actions: {

updateUserName(name) {

// 调用 user Store 中的 actions

const userStore = useUserStore()

userStore.setName(name)

}

}

})

在组件中使用:

html 复制代码
<template>

<div>

<p>{{ profileStore.userInfo }}</p>

<button @click="profileStore.updateUserName('李四')">修改姓名</button>

</div>

</template>

<script setup>

import { useProfileStore } from './stores/profile'

const profileStore = useProfileStore()

</script>

通过这种方式,不同的 Store 可以相互协作,共同管理应用的状态。

六、Pinia 与组合式 API

Pinia 可以与 Vue 3 的组合式 API 无缝配合,在 setup 函数中,我们可以使用 ref、reactive 等创建响应式数据,并将其整合到 Store 中。

6.1 组合式风格的 Store 定义

html 复制代码
// stores/todo.js

import { defineStore } from 'pinia'

import { ref, computed } from 'vue'

export const useTodoStore = defineStore('todo', () => {

// 状态(使用 ref 定义)

const todos = ref([

{ id: 1, text: '学习 Pinia', done: false }

])

// getters(使用 computed 定义)

const unfinishedTodos = computed(() => {

return todos.value.filter(todo => !todo.done)

})

// actions(普通函数)

const addTodo = (text) => {

todos.value.push({

id: Date.now(),

text,

done: false

})

}

const toggleTodo = (id) => {

const todo = todos.value.find(todo => todo.id === id)

if (todo) {

todo.done = !todo.done

}

}

// 返回需要暴露的状态、getters 和 actions

return {

todos,

unfinishedTodos,

addTodo,

toggleTodo

}

})

这种方式与组合式 API 的写法一致,更加灵活,便于将组件中的逻辑提取到 Store 中复用。

6.2 在组件中使用组合式风格的 Store

html 复制代码
<template>

<div>

<input v-model="newTodoText" type="text" placeholder="添加待办">

<button @click="addTodo">添加</button>

<ul>

<li v-for="todo in todoStore.todos" :key="todo.id">

<span :style="{ textDecoration: todo.done ? 'line-through' : 'none' }">

{{ todo.text }}

</span>

<button @click="todoStore.toggleTodo(todo.id)">

{{ todo.done ? '未完成' : '已完成' }}

</button>

</li>

</ul>

<p>未完成的待办数量:{{ todoStore.unfinishedTodos.length }}</p>

</div>

</template>

<script setup>

import { ref } from 'vue'

import { useTodoStore } from './stores/todo'

const todoStore = useTodoStore()

const newTodoText = ref('')

const addTodo = () => {

if (newTodoText.value.trim()) {

todoStore.addTodo(newTodoText.value)

newTodoText.value = ''

}

}

</script>

七、总结

Pinia 作为 Vue 3 推荐的状态管理工具,以其简洁的 API、良好的 TypeScript 支持和与组合式 API 的兼容性,成为大型 Vue 应用状态管理的理想选择。

通过本文的学习,我们了解了 Pinia 的基本概念、安装配置、Store 的创建与使用、状态修改与重置、Store 组合以及与组合式 API 的配合等内容。在实际开发中,应根据应用规模合理划分 Store,将共享状态放入 Store 中管理,非共享状态则保留在组件内部,以保持代码的清晰和高效。

八、结语:

下一章学习 Vue 3 中的路由管理,Vue Router 是官方的路由工具,它能帮助我们实现单页面应用中的页面跳转和路由管理,我会详细讲解其基本用法和常见场景。如果宝子们有其他想先了解的内容,也可以评论区留言。

相关推荐
燕山石头20 分钟前
解决 IntelliJ IDEA Build时 Lombok 不生效问题
java·前端·intellij-idea
chancygcx_26 分钟前
前端核心技术Node.js(二)——path模块、HTTP与模块化
前端·http·node.js
YGY_Webgis糕手之路28 分钟前
Cesium 快速入门(三)Viewer:三维场景的“外壳”
前端·gis·cesium
丘色果38 分钟前
NPM打包时,报reason: getaddrinfo ENOTFOUND registry.nlark.com
前端·npm·node.js
姜太小白1 小时前
【前端】CSS Flexbox布局示例介绍
前端·css
我命由我123451 小时前
Spring Boot 项目问题:Web server failed to start. Port 5566 was already in use.
java·前端·jvm·spring boot·后端·spring·java-ee
南囝coding1 小时前
最近Vibe Coding的经验总结
前端·后端·程序员
前端小咸鱼一条2 小时前
React组件化的封装
前端·javascript·react.js
随便起的名字也被占用2 小时前
leaflet中绘制轨迹线的大量轨迹点,解决大量 marker 绑定 tooltip 同时显示导致的性能问题
前端·javascript·vue.js·leaflet
南方kenny2 小时前
TypeScript + React:让前端开发更可靠的黄金组合
前端·react.js·typescript