Vue Pinia 状态管理实战指南
文章目录
- Vue Pinia 状态管理实战指南
- 为什么选择 Pinia?
- 基础使用
- 安装和配置
- 定义 Store
- 在组件中使用
- 四个实战场景
- 1. 用户状态管理
- 2. 购物车功能
- 3. 主题设置
- 4. API 数据管理
- Pinia vs Vuex 对比
- 最佳实践
- 1. Store 命名规范
- 2. 状态结构设计
- 3. 组合多个 Store
- 4. 持久化存储
- 常见问题
- 1. 状态重置
- 2. 监听状态变化
- 3. 服务端渲染 (SSR)
- 总结
Vue Pinia 状态管理实战指南
轻量、直观、类型安全的 Vue 状态管理方案
为什么选择 Pinia?
Pinia 是 Vue 官方推荐的状态管理库,相比 Vuex 有以下优势:
- 更简单的 API:无需 mutations,直接修改状态
- 完美的 TypeScript 支持:天然类型推导
- 模块化设计:每个 store 都是独立的
- 开发工具友好:更好的调试体验
基础使用
安装和配置
npm install pinia
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const app = createApp(App)
app.use(createPinia())
app.mount('#app')
定义 Store
// stores/counter.js
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0,name: 'Counter'}),getters: {doubleCount: (state) => state.count * 2,greeting: (state) => `Hello, ${state.name}!`},actions: {increment() {this.count++},async fetchData() {// 异步操作const data = await fetch('/api/data')this.count = data.count}}
})
在组件中使用
<template><div><p>计数: {{ counter.count }}</p><p>双倍: {{ counter.doubleCount }}</p><button @click="counter.increment()">增加</button></div>
</template><script setup>
import { useCounterStore } from '@/stores/counter'const counter = useCounterStore()
</script>
四个实战场景
1. 用户状态管理
// stores/user.js
import { defineStore } from 'pinia'export const useUserStore = defineStore('user', {state: () => ({user: null,token: localStorage.getItem('token'),isLoading: false}),getters: {isAuthenticated: (state) => !!state.token,userName: (state) => state.user?.name || '游客'},actions: {async login(credentials) {this.isLoading = truetry {const response = await fetch('/api/login', {method: 'POST',body: JSON.stringify(credentials)})const data = await response.json()this.user = data.userthis.token = data.tokenlocalStorage.setItem('token', data.token)} finally {this.isLoading = false}},logout() {this.user = nullthis.token = nulllocalStorage.removeItem('token')}}
})
2. 购物车功能
// stores/cart.js
import { defineStore } from 'pinia'export const useCartStore = defineStore('cart', {state: () => ({items: []}),getters: {totalItems: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0),totalPrice: (state) => state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)},actions: {addItem(product) {const existingItem = this.items.find(item => item.id === product.id)if (existingItem) {existingItem.quantity++} else {this.items.push({ ...product, quantity: 1 })}},removeItem(productId) {const index = this.items.findIndex(item => item.id === productId)if (index > -1) {this.items.splice(index, 1)}},clearCart() {this.items = []}}
})
3. 主题设置
// stores/theme.js
import { defineStore } from 'pinia'export const useThemeStore = defineStore('theme', {state: () => ({isDark: localStorage.getItem('theme') === 'dark'}),getters: {theme: (state) => state.isDark ? 'dark' : 'light'},actions: {toggleTheme() {this.isDark = !this.isDarklocalStorage.setItem('theme', this.theme)document.documentElement.setAttribute('data-theme', this.theme)},setTheme(theme) {this.isDark = theme === 'dark'localStorage.setItem('theme', theme)document.documentElement.setAttribute('data-theme', theme)}}
})
4. API 数据管理
// stores/posts.js
import { defineStore } from 'pinia'export const usePostsStore = defineStore('posts', {state: () => ({posts: [],loading: false,error: null}),getters: {publishedPosts: (state) => state.posts.filter(post => post.published),getPostById: (state) => (id) => state.posts.find(post => post.id === id)},actions: {async fetchPosts() {this.loading = truethis.error = nulltry {const response = await fetch('/api/posts')this.posts = await response.json()} catch (error) {this.error = error.message} finally {this.loading = false}},async createPost(postData) {const response = await fetch('/api/posts', {method: 'POST',body: JSON.stringify(postData)})const newPost = await response.json()this.posts.push(newPost)}}
})
Pinia vs Vuex 对比
| 特性 | Pinia | Vuex |
|---|---|---|
| API 复杂度 | 简单直观 | 相对复杂 |
| TypeScript | 原生支持 | 需要额外配置 |
| 模块化 | 天然模块化 | 需要 modules |
| 状态修改 | 直接修改 | 必须通过 mutations |
| 异步操作 | actions 中直接处理 | 需要 actions + mutations |
// Pinia - 简洁直观
const store = useStore()
store.count++
store.updateUser(userData)// Vuex - 需要 commit
store.commit('INCREMENT')
store.dispatch('updateUser', userData)
最佳实践
1. Store 命名规范
// 推荐:use + 功能名 + Store
export const useUserStore = defineStore('user', {})
export const useCartStore = defineStore('cart', {})
export const useThemeStore = defineStore('theme', {})
2. 状态结构设计
// 推荐:扁平化状态结构
state: () => ({user: null,isLoading: false,error: null
})// 避免:过度嵌套
state: () => ({user: {profile: {personal: {name: ''}}}
})
3. 组合多个 Store
<script setup>
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'const userStore = useUserStore()
const cartStore = useCartStore()// 可以在 actions 中调用其他 store
const handlePurchase = () => {if (userStore.isAuthenticated) {cartStore.clearCart()}
}
</script>
4. 持久化存储
// 简单的持久化实现
export const useSettingsStore = defineStore('settings', {state: () => ({language: 'zh-CN',notifications: true}),actions: {updateSettings(settings) {Object.assign(this, settings)localStorage.setItem('settings', JSON.stringify(this.$state))},loadSettings() {const saved = localStorage.getItem('settings')if (saved) {Object.assign(this, JSON.parse(saved))}}}
})
常见问题
1. 状态重置
// 重置整个 store
const store = useStore()
store.$reset()// 重置特定状态
store.$patch({count: 0,name: ''
})
2. 监听状态变化
// 在组件中监听
import { watch } from 'vue'const store = useStore()watch(() => store.count,(newCount) => {console.log('Count changed:', newCount)}
)
3. 服务端渲染 (SSR)
// 在 SSR 中使用
export const useStore = defineStore('main', {state: () => ({data: null}),actions: {async hydrate() {if (process.client && !this.data) {await this.fetchData()}}}
})
总结
Pinia 的核心优势:
- 简单易用:API 设计直观,学习成本低
- 类型安全:完美的 TypeScript 支持
- 性能优秀:按需响应,避免不必要的更新
- 开发体验:优秀的开发工具支持
- 渐进式:可以逐步迁移现有项目
选择 Pinia,让 Vue 状态管理变得更加简单高效!
希望这篇指南能帮你快速上手 Pinia 状态管理!
