Pinia是Vue3的官方状态管理库,是Vuex的继任者。它提供了更简单的API、更好的TypeScript支持和模块化设计。
# npm
npm install pinia
# yarn
yarn add pinia
# pnpm
pnpm add pinia
// 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')
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: '计数器'
}),
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
incrementBy(amount) {
this.count += amount
}
}
})
<template>
<div>
<h2>{{ store.name }}</h2>
<p>计数: {{ store.count }}</p>
<p>双倍: {{ store.doubleCount }}</p>
<button @click="store.increment">+1</button>
<button @click="store.decrement">-1</button>
<button @click="store.incrementBy(10)">+10</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
</script>
// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// state
const count = ref(0)
const name = ref('计数器')
// getters
const doubleCount = computed(() => count.value * 2)
// actions
function increment() {
count.value++
}
function decrement() {
count.value--
}
function incrementBy(amount) {
count.value += amount
}
return {
count,
name,
doubleCount,
increment,
decrement,
incrementBy
}
})
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// 错误:直接解构会失去响应式
const { count, doubleCount } = store
// 正确:使用storeToRefs保持响应式
const { count, doubleCount } = storeToRefs(store)
// actions可以直接解构
const { increment, decrement } = store
</script>
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// 方式1:直接修改
store.count++
// 方式2:使用$patch对象
store.$patch({
count: store.count + 1,
name: '新名称'
})
// 方式3:使用$patch函数
store.$patch((state) => {
state.count++
state.name = '新名称'
})
// 方式4:使用action(推荐)
store.increment()
</script>
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// 重置到初始状态
store.$reset()
</script>
<script setup>
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
// 订阅state变化
store.$subscribe((mutation, state) => {
console.log('mutation类型:', mutation.type)
console.log('新状态:', state)
// 持久化到localStorage
localStorage.setItem('counter', JSON.stringify(state))
})
// 订阅actions
store.$onAction(({ name, args, after, onError }) => {
console.log(`Action ${name} 被调用,参数:`, args)
after((result) => {
console.log('Action完成,结果:', result)
})
onError((error) => {
console.error('Action错误:', error)
})
})
</script>
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
users: [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 35 }
]
}),
getters: {
// 自动推导返回类型
userCount: (state) => state.users.length,
// 访问其他getter
userCountMessage() {
return `共有 ${this.userCount} 个用户`
},
// 返回函数(不会缓存)
getUserById: (state) => {
return (userId) => state.users.find(u => u.id === userId)
},
// 访问其他store
otherStoreGetter() {
const otherStore = useOtherStore()
return otherStore.someValue
}
}
})
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
users: [],
loading: false,
error: null
}),
actions: {
async fetchUsers() {
this.loading = true
this.error = null
try {
const response = await fetch('/api/users')
this.users = await response.json()
} catch (error) {
this.error = error.message
} finally {
this.loading = false
}
},
async createUser(userData) {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
})
const newUser = await response.json()
this.users.push(newUser)
return newUser
}
}
})
// stores/cart.js
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { useProductStore } from './product'
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
getters: {
total() {
const productStore = useProductStore()
return this.items.reduce((sum, item) => {
const product = productStore.getProductById(item.productId)
return sum + (product?.price || 0) * item.quantity
}, 0)
}
},
actions: {
addItem(productId, quantity = 1) {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
throw new Error('请先登录')
}
const existingItem = this.items.find(i => i.productId === productId)
if (existingItem) {
existingItem.quantity += quantity
} else {
this.items.push({ productId, quantity })
}
}
}
})
// plugins/persist.js
export function persistPlugin({ store }) {
// 从localStorage恢复
const saved = localStorage.getItem(store.$id)
if (saved) {
store.$patch(JSON.parse(saved))
}
// 订阅变化并保存
store.$subscribe((mutation, state) => {
localStorage.setItem(store.$id, JSON.stringify(state))
})
}
// main.js
import { createPinia } from 'pinia'
import { persistPlugin } from './plugins/persist'
const pinia = createPinia()
pinia.use(persistPlugin)
app.use(pinia)
// stores/auth.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(localStorage.getItem('token'))
const loading = ref(false)
const isLoggedIn = computed(() => !!token.value)
const userName = computed(() => user.value?.name || '游客')
async function login(credentials) {
loading.value = true
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
})
const data = await response.json()
token.value = data.token
user.value = data.user
localStorage.setItem('token', data.token)
return true
} catch (error) {
console.error('登录失败:', error)
return false
} finally {
loading.value = false
}
}
function logout() {
user.value = null
token.value = null
localStorage.removeItem('token')
}
async function fetchUser() {
if (!token.value) return
try {
const response = await fetch('/api/user', {
headers: { Authorization: `Bearer ${token.value}` }
})
user.value = await response.json()
} catch (error) {
logout()
}
}
return {
user,
token,
loading,
isLoggedIn,
userName,
login,
logout,
fetchUser
}
})
// stores/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const itemCount = computed(() => {
return items.value.reduce((sum, item) => sum + item.quantity, 0)
})
const total = computed(() => {
return items.value.reduce((sum, item) => {
return sum + item.price * item.quantity
}, 0)
})
function addItem(product) {
const existingItem = items.value.find(i => i.id === product.id)
if (existingItem) {
existingItem.quantity++
} else {
items.value.push({
id: product.id,
name: product.name,
price: product.price,
quantity: 1
})
}
}
function removeItem(productId) {
const index = items.value.findIndex(i => i.id === productId)
if (index > -1) {
items.value.splice(index, 1)
}
}
function updateQuantity(productId, quantity) {
const item = items.value.find(i => i.id === productId)
if (item) {
item.quantity = quantity
if (item.quantity <= 0) {
removeItem(productId)
}
}
}
function clear() {
items.value = []
}
return {
items,
itemCount,
total,
addItem,
removeItem,
updateQuantity,
clear
}
})