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>
用户管理Store示例
一个更复杂的用户管理store示例:
// src/stores/user.js
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
// 状态
const user = ref(null)
const loading = ref(false)
const error = ref(null)
// 计算属性
const isLoggedIn = computed(() => !!user.value)
const username = computed(() => user.value?.name || '游客')
// Actions
async function login(credentials) {
loading.value = true
error.value = null
try {
// 模拟API调用
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
if (!response.ok) {
throw new Error('登录失败')
}
user.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
function logout() {
user.value = null
}
return {
user,
loading,
error,
isLoggedIn,
username,
login,
logout
}
})
10. 持久化Store数据
在实际应用中,我们经常需要将store数据持久化到localStorage,以便在页面刷新后恢复状态。可以使用pinia-plugin-persistedstate插件:
npm install pinia-plugin-persistedstate
设置插件:
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
const app = createApp(App)
app.use(pinia)
app.mount('#app')
在store中启用持久化:
export const useUserStore = defineStore('user', () => {
// store内容...
}, {
persist: true // 启用持久化
})
也可以自定义持久化选项:
export const useUserStore = defineStore('user', () => {
// store内容...
}, {
persist: {
key: 'user-store', // 自定义存储的key
storage: localStorage, // 使用的存储方式
paths: ['user', 'isLoggedIn'] // 只持久化特定字段
}
})
11. 最佳实践
-
按功能划分Store:不要创建一个巨大的store,而是按照功能模块划分多个小的store
-
使用组合式API风格:推荐使用组合式API风格定义store,更符合Vue 3的设计理念
-
保持Store简洁:store应该只包含需要在多个组件之间共享的状态
-
使用TypeScript:Pinia对TypeScript有很好的支持,可以提供更好的开发体验
-
使用DevTools调试:Pinia集成了Vue DevTools,可以方便地调试store
-
考虑持久化:对于需要持久化的数据,可以使用插件将store数据保存到localStorage
12. 常见问题解答
Q: Pinia和Vuex可以一起使用吗?
A: 可以,但不推荐。它们都是状态管理库,同时使用会增加复杂性。
Q: 如何在Pinia中实现模块化?
A: Pinia天然支持模块化,每个store就是一个模块 ,不需要像Vuex那样嵌套模块。
Q: 如何在Pinia中处理异步操作?
A: 在actions中直接使用async/await或Promise处理异步操作。
Q: 如何测试Pinia store?
A: Pinia提供了测试工具,可以轻松测试store。详见官方文档的测试章节。
总结
Pinia是一个现代化的Vue状态管理库,它简化了状态管理的复杂性,提供了更好的开发体验。与Vuex相比,Pinia更加轻量、简单,并且对TypeScript有更好的支持。
通过本文,你应该已经掌握了Pinia的基本用法,包括创建store、在组件中使用store、修改状态等。随着你的应用变得更加复杂,Pinia将帮助你更好地管理应用状态。
记住,状态管理是一个强大的工具,但不是所有状态都需要放在全局store中。对于只在单个组件中使用的状态,仍然可以使用组件的本地状态。