pinia状态管理工具
pinia状态管理工具
Pinia 是 Vue.js 官方推荐的新一代状态管理库,可以看作是 Vuex 的替代品。
1. 什么是 Pinia?
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。由 Vue.js 核心团队维护,并且对 TypeScript 有着极其出色的支持。
核心设计理念:
- 直观: 像定义组件一样定义 Store,API 设计尽可能简单直观。
- 类型安全: 全程提供出色的 TypeScript 支持,无需复杂的包装器。
- 模块化: 可以拥有多个 Store,并自然地编码,让 Store 自动打包。
- 轻量: 体积非常小(约 1KB),几乎不会增加打包负担。
- Composition API: 完美契合 Vue 3 的 Composition API,但同时也支持 Option API。
2. 核心概念(与 Vuex 对比)
Pinia 的核心概念比 Vuex 更加简化,去掉了容易令人混淆的 mutations
。
概念 | Vuex | Pinia | 说明 |
---|---|---|---|
状态 | state | state | 存储应用状态数据的地方 |
获取状态 | getters | getters | 用于计算和派生状态,相当于 Store 的计算属性 |
修改状态 | mutations | actions | 这是最大的不同。Pinia 中 actions 既可用于同步,也可用于异步操作 |
异步操作 | actions | actions | 在 Pinia 中,异步和同步操作都在 actions 中完成 |
简单来说,Pinia 就是:state
+ getters
+ actions
。
3. 安装与设置
1. 安装:
npm install pinia
# 或
yarn add pinia
2. 在 Vue 应用中创建并引入 Pinia:
在 main.js
或 main.ts
中:
import { createApp } from 'vue'
import { createPinia } from 'pinia' // 导入 createPinia 函数
import App from './App.vue'// 创建 Pinia 实例
const pinia = createPinia()
// 创建 Vue 应用实例
const app = createApp(App)// 使用 Pinia
app.use(pinia)app.mount('#app')
4. 定义一个 Store
Pinia 使用 defineStore()
函数来定义一个 Store,它需要一个唯一名称(必填)作为第一个参数。这个名称是为了在 Devtools 中调试使用。
Store 有两种定义风格,类似于 Vue 的 Option API
和 Composition API
。
方式一:Option Store(选项式风格)
这种方式与 Vuex 的写法非常相似,更容易从 Vuex 迁移过来。
// stores/counter.js
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {// 状态数据state: () => ({count: 0,name: 'Eduardo',}),// 计算属性/派生状态getters: {doubleCount: (state) => state.count * 2,doubleCountPlusOne() { // 可以使用 this 访问整个 store 实例return this.doubleCount + 1},},// 操作方法(同步和异步)actions: {increment() {this.count++ // 通过 this 访问 state},async incrementAsync() {setTimeout(() => {this.increment() // 可以调用其他 action}, 1000)},},
})
方式二:Setup Store(组合式风格)
这种方式使用一个函数来定义 Store,类似于 Vue 的 setup()
函数和 Composition API。
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'export const useCounterStore = defineStore('counter', () => {// state (使用 ref)const count = ref(0)const name = ref('Eduardo')// getters (使用 computed)const doubleCount = computed(() => count.value * 2)const doubleCountPlusOne = computed(() => doubleCount.value + 1)// actions (使用普通函数)function increment() {count.value++}async function incrementAsync() {setTimeout(() => {increment()}, 1000)}// 返回所有需要暴露的状态和方法return {count,name,doubleCount,doubleCountPlusOne,increment,incrementAsync,}
})
如何选择?
- 如果你习惯 Vuex 或 Option API,选 Option Store。
- 如果你喜欢 Composition API 的灵活性,选 Setup Store。
5. 在组件中使用 Store
在组件中,你需要导入并调用你定义的 Store 函数(例如 useCounterStore
)来使用它。Pinia 会自动管理单例。
访问 State 和 Getters
<template><div><p>Count: {{ counter.count }}</p><p>Double Count: {{ counter.doubleCount }}</p><p>Double Count Plus One: {{ counter.doubleCountPlusOne }}</p><p>Name: {{ name }}</p> <!-- 直接解构的 name --></div>
</template><script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia' // 重要!用于保持响应式// 在 setup 中调用 Store 函数
const counter = useCounterStore()// ❌ 错误:直接解构会失去响应式!
// const { count, name } = counter// ✅ 正确:使用 storeToRefs 解构可以保持响应式
const { count, name } = storeToRefs(counter)
// 注意:actions 不需要也不应该用 storeToRefs 解构,直接通过 counter 调用即可
</script>
调用 Actions
<template><div><button @click="counter.increment()">Increment</button><button @click="counter.incrementAsync()">Increment Async</button></div>
</template><script setup>
import { useCounterStore } from '@/stores/counter'const counter = useCounterStore()// 也可以在方法或事件处理逻辑中调用
function handleClick() {counter.increment()
}
</script>
6. 状态修改与响应式
- 直接修改: 你可以直接修改状态(
counter.count++
),这在 Pinia 中是允许的。 - 批量修改: 使用
$patch
方法进行批量更新,这有利于性能优化,并且 Devtools 会将其记录为一次修改。
const counter = useCounterStore()// 方式一:传入一个部分 state 对象
counter.$patch({count: counter.count + 1,name: 'New Name',
})// 方式二:传入一个修改函数,适用于修改数组或嵌套对象
counter.$patch((state) => {state.items.push({ name: 'shoes', quantity: 1 })state.hasChanged = true
})
- 替换整个状态: 可以通过
$state
属性替换整个 store 的状态。
counter.$state = { count: 1000, name: 'Paimon' }
7. 高级特性与技巧
订阅状态变化
可以使用 $subscribe
来监听 state 及其变化,类似于 Vuex 的 subscribe。常用于持久化等操作。
counter.$subscribe((mutation, state) => {// mutation 包含了修改的信息(events, type, storeId)// state 是修改后的新状态localStorage.setItem('counter', JSON.stringify(state))
})
订阅 Action
可以使用 $onAction
来监听 action 及其结果。
const unsubscribe = counter.$onAction(({ name, store, args, after, onError }) => {// action 调用前执行console.log(`Start "${name}" with params [${args.join(', ')}].`)// after 在 action 成功并返回后执行after((result) => {console.log(`Finished "${name}". Result was: ${result}.`)})// onError 在 action 抛出错误或 reject 时执行onError((error) => {console.warn(`Failed "${name}": ${error}.`)})
})// 手动移除订阅
// unsubscribe()
模块化(多 Store)
Pinia 天生是模块化的。你只需要定义多个不同的 Store 并在不同组件中引入它们即可。
// stores/user.js
export const useUserStore = defineStore('user', {state: () => ({ user: null }),// ...
})// stores/cart.js
export const useCartStore = defineStore('cart', {state: () => ({ items: [] }),// ...
})
然后在组件中可以同时使用多个 Store:
<script setup>
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'const userStore = useUserStore()
const cartStore = useCartStore()
// 甚至可以在一个 Store 中使用另一个 Store
// (注意避免循环引用)
</script>
总结:为什么选择 Pinia?
- 更简单的 API: 去除
mutations
,只有state
,getters
,actions
,概念更清晰。 - 完美的 TS 支持: 无需复杂配置,类型推断非常强大。
- 模块化设计: 不需要嵌套模块,多个 Store 自然拆分。
- 轻量级: 体积极小,对应用打包体积影响几乎可以忽略不计。
- Composition API: 与 Vue 3 的编程思想完美契合,使用起来更加灵活。
- 官方推荐: 作为 Vuex 的继任者,是未来 Vue 生态的状态管理首选。
对于新项目,强烈推荐直接使用 Pinia。对于老项目,如果 Vuex 能满足需求且没有维护性问题,不一定需要立即迁移,但 Pinia 绝对是未来趋势。