Vue 2 + Pinia 2 完全教程

第一章:Pinia 基础概念

1.1 Pinia 是什么?

Pinia 是 Vue 的存储库,它允许你跨组件/页面共享状态。它是 Vuex 的替代方案,提供了更简单的 API 和更好的 TypeScript 支持。

练习 1.1:基本状态管理

创建一个简单的计数器应用。

store/counter.js
import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), getters: { doubleCount: (state) => state.count * 2 }, actions: { increment() { this.count++ }, decrement() { this.count-- } } })
main.js
import Vue from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const pinia = createPinia() Vue.use(pinia) new Vue({ pinia, render: h => h(App) }).$mount('#app')
App.vue
<template> <div id="app"> <h2>计数器: {{ count }}</h2> <p>双倍计数: {{ doubleCount }}</p> <button @click="increment">增加</button> <button @click="decrement">减少</button> </div> </template> <script> import { useCounterStore } from './store/counter' import { mapState, mapActions } from 'pinia' export default { computed: { ...mapState(useCounterStore, ['count', 'doubleCount']) }, methods: { ...mapActions(useCounterStore, ['increment', 'decrement']) } } </script>

第二章:核心概念

2.1 State

State 是 store 的数据源。

练习 2.1:用户状态管理

创建一个用户状态管理store。

store/user.js
import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ user: null, isAuthenticated: false }), getters: { username: (state) => state.user?.username || '未登录' }, actions: { login(userData) { this.user = userData this.isAuthenticated = true }, logout() { this.user = null this.isAuthenticated = false } } })

2.2 Getters

Getters 用于从 state 中派生出一些状态。

练习 2.2:购物车计算

创建一个购物车store,包含商品列表和计算属性。

store/cart.js
import { defineStore } from 'pinia' export const useCartStore = defineStore('cart', { state: () => ({ items: [] }), getters: { totalItems: (state) => state.items.length, totalPrice: (state) => state.items.reduce((total, item) => total + item.price * item.quantity, 0), hasItems: (state) => state.items.length > 0 }, actions: { addItem(item) { const existingItem = this.items.find(i => i.id === item.id) if (existingItem) { existingItem.quantity++ } else { this.items.push({ ...item, quantity: 1 }) } }, removeItem(itemId) { this.items = this.items.filter(item => item.id !== itemId) } } })

2.3 Actions

Actions 用于处理异步操作和修改 state。

练习 2.3:异步数据获取

创建一个处理异步数据获取的store。

store/products.js
import { defineStore } from 'pinia' export const useProductsStore = defineStore('products', { state: () => ({ products: [], loading: false, error: null }), actions: { async fetchProducts() { this.loading = true try { // 模拟API调用 const response = await fetch('https://api.example.com/products') const data = await response.json() this.products = data } catch (error) { this.error = error.message } finally { this.loading = false } } } })

2.4 模块化

将store拆分为多个模块,便于管理。

练习 2.4:模块化store

创建一个模块化的store结构。

store/index.js
import { useCounterStore } from './counter' import { useUserStore } from './user' import { useCartStore } from './cart' import { useProductsStore } from './products' export { useCounterStore, useUserStore, useCartStore, useProductsStore }

第三章:综合案例

3.1 电商应用状态管理

通过一个完整的电商应用案例,综合运用 Pinia 的所有核心概念。

练习 3.1:电商应用状态管理实现

实现以下功能:

store/auth.js
import { defineStore } from 'pinia' export const useAuthStore = defineStore('auth', { state: () => ({ user: null, token: null, isAuthenticated: false }), actions: { async login(credentials) { try { const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify(credentials) }) const data = await response.json() this.user = data.user this.token = data.token this.isAuthenticated = true localStorage.setItem('token', data.token) } catch (error) { throw error } }, logout() { this.user = null this.token = null this.isAuthenticated = false localStorage.removeItem('token') } } })
store/orders.js
import { defineStore } from 'pinia' import { useCartStore } from './cart' export const useOrdersStore = defineStore('orders', { state: () => ({ orders: [], currentOrder: null }), actions: { async createOrder(shippingInfo) { const cartStore = useCartStore() try { const response = await fetch('/api/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items: cartStore.items, shippingInfo }) }) const order = await response.json() this.orders.push(order) cartStore.clearCart() return order } catch (error) { throw error } } } })

第四章:最佳实践

4.1 状态持久化

使用 pinia-plugin-persistedstate 实现状态持久化。

main.js (持久化配置)
import Vue from 'vue' import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import App from './App.vue' const pinia = createPinia() pinia.use(piniaPluginPersistedstate) Vue.use(pinia) new Vue({ pinia, render: h => h(App) }).$mount('#app')

4.2 状态订阅

使用 $subscribe 监听状态变化。

状态订阅示例
import { useCartStore } from './store/cart' export default { mounted() { const cartStore = useCartStore() cartStore.$subscribe((mutation, state) => { console.log('购物车状态变化:', state) }) } }

4.3 状态重置

使用 $reset 重置store状态。

状态重置示例
import { useUserStore } from './store/user' export default { methods: { resetUserState() { const userStore = useUserStore() userStore.$reset() } } }

第五章:常见问题解答

5.1 常见错误

列举并解决使用 Pinia 时常见的错误。

常见错误及解决方案
// 错误1:在组件外使用store // ❌ 错误做法 const store = useStore() // 在组件外调用 // ✅ 正确做法 export default { setup() { const store = useStore() return { store } } } // 错误2:直接修改state // ❌ 错误做法 store.state.count = 10 // ✅ 正确做法 store.$patch({ count: 10 }) // 或 store.increment() // 错误3:忘记处理异步错误 // ❌ 错误做法 async fetchData() { const response = await fetch('/api/data') this.data = await response.json() } // ✅ 正确做法 async fetchData() { try { const response = await fetch('/api/data') this.data = await response.json() } catch (error) { this.error = error.message } }

5.2 调试技巧

介绍 Pinia 的调试工具和技巧。

调试技巧示例
// 1. 在组件中打印store状态 export default { mounted() { const store = useStore() console.log('当前状态:', store.$state) } } // 2. 使用 Vue Devtools 查看状态 // 在 main.js 中配置 import { createPinia } from 'pinia' const pinia = createPinia() Vue.use(pinia) // 3. 监听状态变化 export default { mounted() { const store = useStore() store.$subscribe((mutation, state) => { console.log('状态变化:', mutation, state) }) } }

学习建议

建议按照章节顺序学习,每完成一个练习后再进行下一个。遇到问题时,可以查看对应的常见问题解答部分。