一、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 的步骤:
- 导入定义好的 useXXXStore 函数。
- 调用该函数获取 Store 实例(注意:同一个 Store 函数多次调用返回的是同一个实例)。
- 通过实例访问 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 是官方的路由工具,它能帮助我们实现单页面应用中的页面跳转和路由管理,我会详细讲解其基本用法和常见场景。如果宝子们有其他想先了解的内容,也可以评论区留言。