Appearance
Pinia 状态管理
Pinia 是 Vue3 的官方推荐状态管理库,它是 Vuex 的继任者,提供了更简洁的 API 和更好的 TypeScript 支持。
为什么使用 Pinia?
- 简洁的 API:相比 Vuex,Pinia 的 API 更加简洁直观
- 更好的 TypeScript 支持:内置 TypeScript 类型定义
- 模块化设计:支持多个 store,每个 store 都是独立的
- 响应式数据:使用 Vue3 的响应式系统
- DevTools 支持:提供更好的开发工具集成
Pinia 安装
bash
npm install pinia基本配置
在 main.js 中注册 Pinia
javascript
// 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
基本 Store
javascript
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: '计数器'
}),
getters: {
doubleCount: (state) => state.count * 2,
getCountWithName: (state) => {
return `${state.name}: ${state.count}`
}
},
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
reset() {
this.count = 0
},
setName(name) {
this.name = name
}
}
})使用 Store
vue
<template>
<div>
<h2>{{ counterStore.getCountWithName }}</h2>
<p>双倍计数: {{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment">增加</button>
<button @click="counterStore.decrement">减少</button>
<button @click="counterStore.reset">重置</button>
<input v-model="name" @input="counterStore.setName(name)" placeholder="输入计数器名称">
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useCounterStore } from './stores/counter'
const counterStore = useCounterStore()
const name = ref(counterStore.name)
</script>Store 的核心概念
State
State 是 Store 中存储数据的地方,它是一个函数,返回一个对象。
javascript
state: () => ({
count: 0,
user: null,
items: []
})Getters
Getters 是计算属性,用于从 State 中派生出新的数据。
javascript
getters: {
// 基本用法
doubleCount: (state) => state.count * 2,
// 接收其他 getters
getCountWithName: (state, getters) => {
return `${state.name}: ${getters.doubleCount}`
}
}Actions
Actions 是用于修改 State 的方法,它可以是同步的,也可以是异步的。
javascript
actions: {
// 同步 action
increment() {
this.count++
},
// 异步 action
async fetchUser() {
try {
const response = await fetch('https://api.example.com/user')
this.user = await response.json()
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
}多个 Store
Pinia 支持创建多个独立的 Store,每个 Store 负责管理不同的状态。
创建多个 Store
javascript
// stores/counter.js
export const useCounterStore = defineStore('counter', {
// ...
})
// stores/user.js
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
loading: false,
error: null
}),
actions: {
async login(username, password) {
this.loading = true
this.error = null
try {
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
})
if (!response.ok) {
throw new Error('登录失败')
}
this.user = await response.json()
localStorage.setItem('token', this.user.token)
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
},
logout() {
this.user = null
localStorage.removeItem('token')
}
}
})使用多个 Store
vue
<template>
<div>
<h2>计数器: {{ counterStore.count }}</h2>
<button @click="counterStore.increment">增加</button>
<div v-if="userStore.user">
<h2>欢迎, {{ userStore.user.name }}</h2>
<button @click="userStore.logout">退出登录</button>
</div>
<div v-else>
<h2>登录</h2>
<input v-model="username" placeholder="用户名">
<input v-model="password" type="password" placeholder="密码">
<button @click="userStore.login(username, password)" :disabled="userStore.loading">
{{ userStore.loading ? '登录中...' : '登录' }}
</button>
<p v-if="userStore.error">{{ userStore.error }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useCounterStore } from './stores/counter'
import { useUserStore } from './stores/user'
const counterStore = useCounterStore()
const userStore = useUserStore()
const username = ref('')
const password = ref('')
</script>数据持久化
使用 localStorage 实现数据持久化
javascript
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: parseInt(localStorage.getItem('counter') || '0'),
name: localStorage.getItem('counterName') || '计数器'
}),
actions: {
increment() {
this.count++
this.saveToLocalStorage()
},
decrement() {
this.count--
this.saveToLocalStorage()
},
reset() {
this.count = 0
this.saveToLocalStorage()
},
setName(name) {
this.name = name
this.saveToLocalStorage()
},
saveToLocalStorage() {
localStorage.setItem('counter', this.count.toString())
localStorage.setItem('counterName', this.name)
}
}
})使用插件实现数据持久化
也可以使用第三方插件如 pinia-plugin-persistedstate 来实现数据持久化:
bash
npm install pinia-plugin-persistedstatejavascript
// 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')javascript
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: '计数器'
}),
// 启用持久化
persist: true,
// 或者自定义持久化配置
// persist: {
// key: 'counter',
// storage: localStorage,
// paths: ['count'] // 只持久化 count 属性
// },
actions: {
increment() {
this.count++
},
// 其他 actions...
}
})实战示例
购物车 Store
javascript
// stores/cart.js
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
items: [],
total: 0
}),
getters: {
itemCount: (state) => state.items.length,
totalPrice: (state) => {
return state.items.reduce((total, item) => {
return total + item.price * item.quantity
}, 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 })
}
this.updateTotal()
},
removeItem(itemId) {
this.items = this.items.filter(item => item.id !== itemId)
this.updateTotal()
},
updateQuantity(itemId, quantity) {
const item = this.items.find(i => i.id === itemId)
if (item) {
item.quantity = quantity
this.updateTotal()
}
},
clearCart() {
this.items = []
this.total = 0
},
updateTotal() {
this.total = this.totalPrice
}
},
persist: true
})使用购物车 Store
vue
<template>
<div>
<h2>购物车</h2>
<div v-if="cartStore.itemCount === 0">
<p>购物车是空的</p>
</div>
<div v-else>
<ul>
<li v-for="item in cartStore.items" :key="item.id">
<div>{{ item.name }}</div>
<div>价格: ¥{{ item.price }}</div>
<div>
<button @click="cartStore.updateQuantity(item.id, item.quantity - 1)" :disabled="item.quantity <= 1">-</button>
<span>{{ item.quantity }}</span>
<button @click="cartStore.updateQuantity(item.id, item.quantity + 1)">+</button>
</div>
<button @click="cartStore.removeItem(item.id)">删除</button>
</li>
</ul>
<div class="total">
<h3>总计: ¥{{ cartStore.totalPrice }}</h3>
<button @click="cartStore.clearCart">清空购物车</button>
</div>
</div>
<h3>商品列表</h3>
<ul>
<li v-for="product in products" :key="product.id">
<div>{{ product.name }}</div>
<div>价格: ¥{{ product.price }}</div>
<button @click="cartStore.addItem(product)">加入购物车</button>
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useCartStore } from './stores/cart'
const cartStore = useCartStore()
const products = ref([
{ id: 1, name: '商品1', price: 100 },
{ id: 2, name: '商品2', price: 200 },
{ id: 3, name: '商品3', price: 300 }
])
</script>
<style scoped>
.total {
margin-top: 20px;
padding: 10px;
border-top: 1px solid #ccc;
}
</style>通过本章的学习,我们掌握了 Pinia 的基本使用方法,包括创建 Store、使用 State、Getters 和 Actions,以及实现数据持久化等。Pinia 是 Vue3 推荐的状态管理库,它提供了简洁的 API 和良好的 TypeScript 支持,是构建复杂 Vue 应用的有力工具。
