Vue3 Pinia 状态管理实战教程(十一)
- Vue.js
- 9小时前
- 3热度
- 0评论
在 Vue3 中,状态管理是一个重要的组成部分,它帮助开发者在复杂的大型应用中管理和维护应用状态。Pinia 作为 Vue3 官方推荐的状态管理库,以其简洁的 API 设计和优秀的 TypeScript 支持,成为了许多开发者的首选。本文将详细介绍如何在 Vue3 项目中使用 Pinia,并通过实际案例展示其强大功能。
Pinia 简介
Pinia 是一个轻量级的状态管理库,专为 Vue3 设计。与传统的 Vuex 相比,Pinia 提供了更加简洁和直观的 API,更适合 Vue3 的组合式 API 思维。以下是 Pinia 的一些核心特点:
- 支持 Vue2 和 Vue3:无论是 Vue2 还是 Vue3 项目,Pinia 都能无缝集成。
- 极简的 API 设计:Pinia 的 API 设计非常简洁,易于上手。
- 完整的 TypeScript 支持:Pinia 提供了强大的 TypeScript 支持,确保类型安全。
- 支持组合式 API:Pinia 完美兼容 Vue3 的组合式 API,使得状态管理更加灵活。
- 模块化设计:Pinia 的 store 是独立的,无需嵌套模块,减少了命名空间的负担。
安装与配置
创建项目
首先,我们需要使用 Vite 创建一个新的 Vue3 项目:
npm create vite@latest vue-pinia-demo --template vue
cd vue-pinia-demo
npm install安装 Pinia
接下来,在项目中安装 Pinia:
npm install pinia
# 或者使用 yarn
yarn add pinia配置 Pinia
在 src/main.js 或 src/main.ts 中配置 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')创建第一个 Store
在 Pinia 中,Store 是一个数据仓库,用于存储和管理状态。我们来创建一个简单的计数器 Store。
定义 Store
在 src/stores 目录下创建 useCounter.js 文件:
// stores/useCounter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// state: 存储数据
state: () => ({
count: 0,
name: '我的计数器'
}),
// getters: 基于 state 的计算属性
getters: {
doubleCount(state) {
return state.count * 2
},
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
// actions: 执行逻辑、修改 state 的方法
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
incrementBy(amount) {
this.count += amount
},
async incrementAsync() {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000))
this.count++
}
}
})Store 的结构说明
让我们通过一个表格来理解 Store 的各个部分:
| 部分 | 作用 | 示例 |
|---|---|---|
| state | 定义存储的数据 | count: 0 |
| getters | 基于 state 的计算属性 | doubleCount: state => state.count * 2 |
| actions | 修改 state 的方法 | increment() { this.count++ } |
在组件中使用 Store
基本使用
在 src/components 目录下创建 CounterComponent.vue 文件:
<!-- CounterComponent.vue -->
<template>
<div class="counter">
<h3>{{ store.name }}</h3>
<p>当前计数: {{ store.count }}</p>
<p>双倍计数: {{ store.doubleCount }}</p>
<p>双倍加一: {{ store.doubleCountPlusOne }}</p>
<button @click="store.increment()">+1</button>
<button @click="store.decrement()">-1</button>
<button @click="store.incrementBy(5)">+5</button>
<button @click="store.incrementAsync()">异步 +1</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounterStore } from '../stores/useCounter'
const store = useCounterStore()
// 重置状态的方法
function reset() {
store.$reset() // $reset 方法可以重置 state 到初始值
}
</script>修改 src/App.vue 代码如下:
<script setup>
import CounterComponent from './components/CounterComponent.vue'
</script>
<template>
<CounterComponent />
</template>运行项目:
npm run dev在浏览器中访问 http://localhost:5173/,查看效果。
响应式解构
如果你想在模板中直接使用 state 的属性,可以使用 storeToRefs:
<template>
<div>
<p>计数: {{ count }}</p>
<p>名称: {{ name }}</p>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// 使用 storeToRefs 保持响应式
const { count, name } = storeToRefs(store)
// 注意:直接解构会失去响应式!
// 错误写法:const { count, name } = store
</script>组合式 API 风格的 Store
Pinia 也支持使用组合式 API 的风格来定义 Store。以下是一个用户 Store 的示例:
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// state
const user = ref(null)
const isLoggedIn = ref(false)
// getters
const userName = computed(() => user.value?.name || '游客')
const userAge = computed(() => user.value?.age || 0)
// actions
function login(userData) {
user.value = userData
isLoggedIn.value = true
}
function logout() {
user.value = null
isLoggedIn.value = false
}
return {
user,
isLoggedIn,
userName,
userAge,
login,
logout
}
})Store 之间的交互
多个 Store 之间可以相互调用。以下是一个购物车 Store 的示例,展示了如何调用其他 Store 的方法:
// stores/cart.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
actions: {
addItem(product) {
this.items.push(product)
// 调用其他 store 的 action
const userStore = useUserStore()
if (userStore.isLoggedIn) {
// 同步到用户购物车
this.syncToUserCart()
}
}
}
})实际应用示例:购物车
让我们创建一个完整的购物车示例,包括产品 Store 和购物车 Store。
产品 Store
// stores/products.js
export const useProductStore = defineStore('products', {
state: () => ({
products: [
{ id: 1, name: '笔记本电脑', price: 5999, stock: 10 },
{ id: 2, name: '智能手机', price: 3999, stock: 20 },
{ id: 3, name: '无线耳机', price: 299, stock: 50 }
]
}),
getters: {
getProductById: (state) => (id) => {
return state.products.find(product => product.id === id)
},
availableProducts: (state) => {
return state.products.filter(product => product.stock > 0)
}
}
})购物车 Store
// stores/cart.js
export const useCartStore = defineStore('cart', {
state: () => ({
items: [],
discount: 0
}),
getters: {
totalItems: (state) => {
return state.items.reduce((total, item) => total + item.quantity, 0)
},
totalPrice: (state) => {
const productStore = useProductStore()
return state.items.reduce((total, item) => {
const product = productStore.getProductById(item.productId)
return total + (product?.price || 0) * item.quantity
}, 0)
},
finalPrice: (state) => {
return state.totalPrice * (1 - state.discount / 100)
}
},
actions: {
addToCart(productId, quantity = 1) {
const existingItem = this.items.find(item => item.productId === productId)
if (existingItem) {
existingItem.quantity += quantity
} else {
this.items.push({ productId, quantity })
}
},
removeFromCart(productId) {
this.items = this.items.filter(item => item.productId !== productId)
},
clearCart() {
this.items = []
this.discount = 0
},
setDiscount(percent) {
this.discount = Math.max(0, Math.min(100, percent))
}
}
})在组件中使用购物车
创建 ShoppingCart.vue 组件:
<!-- ShoppingCart.vue -->
<template>
<div class="shopping-cart">
<h3>购物车 ({{ cart.totalItems }} 件商品)</h3>
<div v-if="cart.items.length === 0">
<p>购物车为空</p>
</div>
<div v-else>
<div v-for="item in cartItems" :key="item.product.id" class="cart-item">
<span>{{ item.product.name }}</span>
<span>¥{{ item.product.price }}</span>
<span>数量: {{ item.quantity }}</span>
<span>小计: ¥{{ item.product.price * item.quantity }}</span>
<button @click="cart.removeFromCart(item.product.id)">删除</button>
</div>
<div class="cart-summary">
<p>总价: ¥{{ cart.totalPrice }}</p>
<p v-if="cart.discount > 0">折扣: {{ cart.discount }}%</p>
<p>实付: ¥{{ cart.finalPrice }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useCartStore } from '@/stores/cart'
import { useProductStore } from '@/stores/products'
const cart = useCartStore()
const products = useProductStore()
// 计算购物车商品详情
const cartItems = computed(() => {
return cart.items.map(item => ({
product: products.getProductById(item.productId),
quantity: item.quantity
})).filter(item => item.product) // 过滤掉不存在的商品
})
</script>最佳实践和注意事项
1. Store 命名规范
为了保持代码的一致性和可读性,建议使用统一的命名规范。例如:
// 好的命名
export const useUserStore = defineStore('user', {
/* ... */
})
export const useProductStore = defineStore('products', {
/* ... */
})
// 避免的命名
export const userStore = defineStore('user', {
/* ... */
})2. 使用 TypeScript
Pinia 提供了强大的 TypeScript 支持,建议在项目中启用 TypeScript,以确保类型安全。
3. 保持 Store 的独立性
尽量保持每个 Store 的独立性,避免过多的依赖关系,这样可以提高代码的可维护性和可测试性。
4. 使用组合式 API
利用 Vue3 的组合式 API,可以使状态管理更加灵活和高效。
总结
通过本文,我们详细介绍了如何在 Vue3 项目中使用 Pinia 进行状态管理。从安装配置到创建 Store,再到在组件中使用 Store,最后通过一个完整的购物车示例展示了 Pinia 的强大功能。希望本文能帮助你在 Vue3 项目中更好地管理和维护应用状态。