Vue 3 + Pinia 3 完全教程

第一章:Pinia 基础概念

1.1 Pinia 是什么?

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

练习 1.1:基本状态管理

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

store/counter.js
import { defineStore } from 'pinia' import { ref, computed } from 'vue' export const useCounterStore = defineStore('counter', () => { const count = ref(0) const doubleCount = computed(() => count.value * 2) function increment() { count.value++ } function decrement() { count.value-- } return { count, doubleCount, increment, decrement } })
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')
App.vue
<template> <div> <h2>计数器: {{ counter.count }}</h2> <p>双倍计数: {{ counter.doubleCount }}</p> <button @click="counter.increment">增加</button> <button @click="counter.decrement">减少</button> </div> </template> <script setup> import { useCounterStore } from './store/counter' const counter = useCounterStore() </script>

第二章:核心概念

2.1 State

State 是 store 的数据源。

练习 2.1:用户状态管理

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

store/user.js
import { defineStore } from 'pinia' import { ref, computed } from 'vue' export const useUserStore = defineStore('user', () => { const user = ref(null) const isAuthenticated = ref(false) const username = computed(() => user.value?.username || '未登录') function login(userData) { user.value = userData isAuthenticated.value = true } function logout() { user.value = null isAuthenticated.value = false } return { user, isAuthenticated, username, login, logout } })

2.2 Getters

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

练习 2.2:购物车计算

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

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

2.3 Actions

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

练习 2.3:异步数据获取

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

store/products.js
import { defineStore } from 'pinia' import { ref } from 'vue' export const useProductsStore = defineStore('products', () => { const products = ref([]) const loading = ref(false) const error = ref(null) async function fetchProducts() { loading.value = true try { const response = await fetch('https://api.example.com/products') const data = await response.json() products.value = data } catch (err) { error.value = err.message } finally { loading.value = false } } return { products, loading, error, fetchProducts } })

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' import { ref } from 'vue' export const useAuthStore = defineStore('auth', () => { const user = ref(null) const token = ref(null) const isAuthenticated = ref(false) async function login(credentials) { try { const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify(credentials) }) const data = await response.json() user.value = data.user token.value = data.token isAuthenticated.value = true localStorage.setItem('token', data.token) } catch (error) { throw error } } function logout() { user.value = null token.value = null isAuthenticated.value = false localStorage.removeItem('token') } return { user, token, isAuthenticated, login, logout } })
store/orders.js
import { defineStore } from 'pinia' import { ref } from 'vue' import { useCartStore } from './cart' export const useOrdersStore = defineStore('orders', () => { const orders = ref([]) const currentOrder = ref(null) const cartStore = useCartStore() async function createOrder(shippingInfo) { 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() orders.value.push(order) cartStore.clearCart() return order } catch (error) { throw error } } return { orders, currentOrder, createOrder } })

第四章:最佳实践

4.1 状态持久化

使用 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 app = createApp(App) const pinia = createPinia() pinia.use(piniaPluginPersistedstate) app.use(pinia) app.mount('#app')

4.2 状态订阅

使用 $subscribe 监听状态变化。

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

4.3 状态重置

使用 $reset 重置store状态。

状态重置示例
<script setup> import { useUserStore } from './store/user' const userStore = useUserStore() function resetUserState() { userStore.$reset() } </script>

第五章:常见问题解答

5.1 常见错误

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

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

5.2 调试技巧

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

调试技巧示例
// 1. 在组件中打印store状态 <script setup> import { useStore } from './store' import { onMounted } from 'vue' const store = useStore() onMounted(() => { console.log('当前状态:', store.$state) }) </script> // 2. 使用 Vue Devtools 查看状态 // 在 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') // 3. 监听状态变化 <script setup> import { useStore } from './store' import { onMounted } from 'vue' const store = useStore() onMounted(() => { store.$subscribe((mutation, state) => { console.log('状态变化:', mutation, state) }) }) </script>

学习建议

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