1. Pinia是什么?
Pinia是Vue官方推荐的状态管理库,是Vuex的替代品,专为Vue 3设计。它提供了一种简单、直观的方式来管理应用程序的状态。
Pinia的特点:
- 🔥 直观简单:API设计简洁,使用起来更加直观
- 🔄 完整的TypeScript支持:自动推断类型,提供更好的开发体验
- 🔌 模块化设计:可以创建多个独立的store,不需要像Vuex那样嵌套模块
- 🛠️ 开发工具支持:可以在Vue DevTools中查看和调试store
- ⚡ 轻量级:体积小,性能好
- 📱 SSR友好:支持服务端渲染
2. 为什么使用Pinia?
当我们的Vue应用变得复杂时,组件之间共享状态会变得困难。虽然可以使用props和emits在父子组件之间传递数据,但当组件层级变深或需要在不相关的组件之间共享数据时,这种方式就变得很麻烦。
Pinia解决了这个问题,它提供了一个中央存储库来管理应用的状态,任何组件都可以访问和修改这些状态。
3. 安装和设置Pinia
安装Pinia
npm install pinia
在Vue应用中设置Pinia
在main.js
中:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
4. 创建Store
Pinia中的store是使用defineStore
函数定义的。每个store都有一个唯一的ID。
基本Store结构
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// 使用组合式API风格定义store
export const useCounterStore = defineStore('counter', () => {
// 状态(state)
const count = ref(0)
// 计算属性(getters)
const doubleCount = computed(() => count.value * 2)
// 动作(actions)
function increment() {
count.value++
}
// 返回需要暴露的内容
return { count, doubleCount, increment }
})
Store的三个核心概念
- State:存储的数据,相当于组件中的
data
- Getters:计算属性,相当于组件中的
computed
- Actions:方法,可以包含异步操作,相当于组件中的
methods
5. 在组件中使用Store
基本用法
<script setup>
import { useCounterStore } from '@/stores/counter'
// 获取store实例
const counterStore = useCounterStore()
// 现在可以在模板中使用store的状态和方法
</script>
<template>
<div>
<p>Count: {{ counterStore.count }}</p>
<p>Double Count: {{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment()">Increment</button>
</div>
</template>
解构Store
如果想要解构store中的属性,需要使用storeToRefs
函数来保持响应性:
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counterStore = useCounterStore()
// 使用storeToRefs保持响应性
const { count, doubleCount } = storeToRefs(counterStore)
// 方法可以直接解构,不需要storeToRefs
const { increment } = counterStore
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment()">Increment</button>
</div>
</template>
6. 修改Store状态
直接修改
可以直接修改store中的状态:
const counterStore = useCounterStore()
counterStore.count++
使用Actions
推荐使用actions来修改状态,特别是当涉及到复杂逻辑或异步操作时:
// 在store中定义action
function increment() {
count.value++
}
// 在组件中调用action
counterStore.increment()
批量修改状态
使用$patch
方法可以一次性修改多个状态:
counterStore.$patch({
count: counterStore.count + 1,
name: 'new name'
})
或者使用函数形式的$patch
:
counterStore.$patch((state) => {
state.count++
state.name = 'new name'
})
7. 高级用法
重置Store
Pinia自动为每个store提供了$reset
方法,可 以将store重置为初始状态:
counterStore.$reset()
订阅状态变化
可以使用$subscribe
方法来监听store状态的变化:
counterStore.$subscribe((mutation, state) => {
// 每次状态变化时触发
console.log('状态变化了:', mutation, state)
})
使用插件扩展Pinia
Pinia支持插件系统,可以扩展store的功能:
const pinia = createPinia()
// 添加一个简单的插件
pinia.use(({ store }) => {
store.hello = 'world'
// 添加一个方法
store.reset = function() {
// 自定义重置逻辑
}
})
8. 与Vuex的对比
特性 | Pinia | Vuex (4.x) |
---|---|---|
模块化 | 天然支持,每个store独立 | 需要嵌套模块 |
TypeScript支持 | 完全支持,自动类型推断 | 有限支持 |
突变(Mutations) | 不需要,直接修改状态 | 必须通过mutations修改 |
开发工具 | 集成Vue DevTools | 集成Vue DevTools |
体积 | 更小(~1.6kb) | 更大(~9.6kb) |
API复杂度 | 简单直观 | 相对复杂 |
9. 实际项目示例
计数器Store示例
// src/stores/counter.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = 0
}
function incrementBy(amount) {
count.value += amount
}
return {
count,
doubleCount,
increment,
decrement,
reset,
incrementBy
}
})
在组件中使用:
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counterStore = useCounterStore()
const { count, doubleCount } = storeToRefs(counterStore)
</script>
<template>
<div>
<h2>Pinia Counter Example</h2>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<div>
<button @click="counterStore.decrement()">-</button>
<button @click="counterStore.increment()">+</button>
</div>
<div>
<button @click="counterStore.incrementBy(10)">+10</button>
<button @click="counterStore.reset()">Reset</button>
</div>
</div>
</template>