Appearance
Pinia快速入门
Pinia是Vue.js的现代化状态管理库,由Vue核心团队成员开发。它提供了一个更简单、更直观的方式来管理应用状态,相比Vuex,Pinia具有更好的TypeScript支持和更简洁的API。
安装Pinia
使用npm安装
bash
npm install pinia
使用yarn安装
bash
yarn add pinia
使用pnpm安装
bash
pnpm add pinia
在Vue应用中集成Pinia
基础设置
javascript
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
TypeScript支持
typescript
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import type { App } from 'vue'
import App from './App.vue'
const app: App = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
创建第一个Store
定义Store
typescript
// stores/counter.ts
import { defineStore } from 'pinia'
// 定义一个名为 'counter' 的store
export const useCounterStore = defineStore('counter', {
// 状态
state: () => {
return {
count: 0,
name: 'Eduardo',
isAdmin: true
}
},
// 计算属性
getters: {
doubleCount: (state) => state.count * 2,
// 访问其他getter
doubleCountPlusOne(): number {
return this.doubleCount + 1
}
},
// 动作
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
async incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000))
this.count++
}
}
})
在组件中使用Store
vue
<!-- components/Counter.vue -->
<template>
<div>
<p>Count: {{ counterStore.count }}</p>
<p>Double Count: {{ counterStore.doubleCount }}</p>
<p>Name: {{ counterStore.name }}</p>
<button @click="counterStore.increment()">Increment</button>
<button @click="counterStore.decrement()">Decrement</button>
<button @click="incrementAsync()">Increment Async</button>
</div>
</template>
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
const incrementAsync = async () => {
await counterStore.incrementAsync()
}
</script>
Store结构详解
State(状态)
State是存储数据的地方,相当于组件中的data。
typescript
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
age: 0,
email: '',
isLoggedIn: false
})
})
Getters(计算属性)
Getters是store的计算属性,用于派生数据。
typescript
export const useUserStore = defineStore('user', {
state: () => ({
name: 'Alice',
age: 25,
hobbies: ['reading', 'swimming', 'coding']
}),
getters: {
// 基本getter
getFullName: (state) => `${state.name}`,
// 使用this访问其他getter
getProfileInfo(): string {
return `${this.getFullName}, ${this.age} years old`
},
// 接收其他参数的getter
getHobbyCount: (state) => state.hobbies.length,
// 带参数的getter
getHobbiesByLength: (state) => (minLength: number) => {
return state.hobbies.filter(hobby => hobby.length >= minLength)
}
}
})
Actions(动作)
Actions是store中的方法,用于改变state或执行异步操作。
typescript
export const useUserStore = defineStore('user', {
state: () => ({
user: null as User | null,
loading: false,
error: null as string | null
}),
actions: {
// 同步action
setUser(user: User) {
this.user = user
this.error = null
},
// 异步action
async fetchUser(userId: string) {
this.loading = true
try {
const response = await fetch(`/api/users/${userId}`)
const userData = await response.json()
this.user = userData
} catch (error) {
this.error = (error as Error).message
} finally {
this.loading = false
}
},
// 清除用户数据
clearUser() {
this.user = null
this.error = null
}
}
})
在组件中使用Store的多种方式
方式1:直接使用
vue
<template>
<div>
<p>{{ counterStore.count }}</p>
<button @click="counterStore.increment()">+1</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
</script>
方式2:使用解构
vue
<template>
<div>
<p>{{ count }}</p>
<p>{{ doubleCount }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const counterStore = useCounterStore()
// 使用storeToRefs保持响应性
const { count, doubleCount } = storeToRefs(counterStore)
const { increment } = counterStore
</script>
方式3:在选项式API中使用
vue
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script>
import { useCounterStore } from '@/stores/counter'
export default {
computed: {
// 使用计算属性访问store数据
count() {
return useCounterStore().count
},
doubleCount() {
return useCounterStore().doubleCount
}
},
methods: {
increment() {
useCounterStore().increment()
}
}
}
</script>
实际应用示例
购物车Store
typescript
// stores/cart.ts
import { defineStore } from 'pinia'
interface CartItem {
id: string
name: string
price: number
quantity: number
}
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as CartItem[],
discount: 0
}),
getters: {
subtotal(): number {
return this.items.reduce(
(total, item) => total + item.price * item.quantity,
0
)
},
total(): number {
return this.subtotal - this.discount
},
itemCount(): number {
return this.items.reduce(
(count, item) => count + item.quantity,
0
)
}
},
actions: {
addItem(item: Omit<CartItem, 'quantity'>) {
const existingItem = this.items.find(i => i.id === item.id)
if (existingItem) {
existingItem.quantity += 1
} else {
this.items.push({ ...item, quantity: 1 })
}
},
removeItem(itemId: string) {
this.items = this.items.filter(item => item.id !== itemId)
},
updateQuantity(itemId: string, quantity: number) {
if (quantity <= 0) {
this.removeItem(itemId)
return
}
const item = this.items.find(i => i.id === itemId)
if (item) {
item.quantity = quantity
}
},
clearCart() {
this.items = []
this.discount = 0
},
setDiscount(amount: number) {
this.discount = Math.min(amount, this.subtotal)
}
}
})
在组件中使用购物车
vue
<template>
<div class="cart">
<h2>购物车 ({{ cartStore.itemCount }} items)</h2>
<div v-for="item in cartStore.items" :key="item.id" class="cart-item">
<span>{{ item.name }} - ¥{{ item.price }} x {{ item.quantity }}</span>
<div>
<button @click="updateQuantity(item.id, item.quantity - 1)">-</button>
<button @click="updateQuantity(item.id, item.quantity + 1)">+</button>
<button @click="removeItem(item.id)">删除</button>
</div>
</div>
<div class="summary">
<p>小计: ¥{{ cartStore.subtotal }}</p>
<p>折扣: -¥{{ cartStore.discount }}</p>
<p>总计: ¥{{ cartStore.total }}</p>
<button @click="checkout" :disabled="cartStore.items.length === 0">
结账
</button>
</div>
</div>
</template>
<script setup>
import { useCartStore } from '@/stores/cart'
const cartStore = useCartStore()
const updateQuantity = (id, quantity) => {
cartStore.updateQuantity(id, quantity)
}
const removeItem = (id) => {
cartStore.removeItem(id)
}
const checkout = () => {
alert(`订单总额: ¥${cartStore.total}`)
cartStore.clearCart()
}
</script>
Pinia DevTools
Pinia与Vue DevTools深度集成,提供了强大的调试功能。
安装Vue DevTools
bash
# Chrome扩展
# 搜索 "Vue.js devtools"
# Firefox扩展
# 搜索 "Vue.js devtools"
调试Store
在DevTools中,你可以:
- 查看当前状态
- 检查状态变化历史
- 提交状态变更
- 时间旅行调试
总结
Pinia提供了一个现代化、直观的状态管理解决方案:
- 简单易用:API设计简洁,学习成本低
- TypeScript友好:提供完整的类型推断
- 开发工具支持:与Vue DevTools完美集成
- 模块化:支持多个独立的store
- 灵活:支持同步和异步操作
通过本章的学习,你应该能够:
- 安装和配置Pinia
- 创建和使用基本的store
- 理解state、getters和actions的概念
- 在组件中正确使用store
接下来的章节将深入探讨Pinia的高级特性。