Pinia状态管理与Vue3组合式API实战配置教程

Pinia是Vue3生态中的官方状态管理库,相比Vuex 4拥有更简洁的API设计、完整的TypeScript类型推导和更好的开发者工具支持。结合Vue3组合式API,Pinia可以实现模块化、类型安全的状态管理方案。本教程涵盖从项目搭建到生产环境优化的完整流程。

Vue3项目初始化与Pinia安装配置

使用Vite创建Vue3 + TypeScript项目并集成Pinia:

npm create vite@latest vue3-pinia-app -- --template vue-ts
cd vue3-pinia-app
npm install pinia
npm install vue-router@4
npm install axios

在main.ts中注册Pinia:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.use(router)
app.mount('#app')

Pinia Store定义与组合式API写法

以用户管理场景为例,定义一个User Store:

// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import axios from 'axios'

interface UserInfo {
id: number
username: string
email: string
roles: string[]
}

export const useUserStore = defineStore('user', () => {
// state
const userInfo = ref<UserInfo | null>(null)
const token = ref<string>('')
const loading = ref<boolean>(false)

// getters
const isLoggedIn = computed(() => !!token.value)
const isAdmin = computed(() =>
userInfo.value?.roles.includes('admin') ?? false
)
const displayName = computed(() =>
userInfo.value?.username ?? '未登录'
)

// actions
async function login(username: string, password: string) {
loading.value = true
try {
const res = await axios.post('/api/login', { username, password })
token.value = res.data.token
userInfo.value = res.data.user
localStorage.setItem('token', token.value)
return true
} catch (error) {
console.error('登录失败:', error)
return false
} finally {
loading.value = false
}
}

function logout() {
userInfo.value = null
token.value = ''
localStorage.removeItem('token')
}

function restoreToken() {
const saved = localStorage.getItem('token')
if (saved) token.value = saved
}

return {
userInfo, token, loading,
isLoggedIn, isAdmin, displayName,
login, logout, restoreToken
}
}, {
persist: true // 需要pinia-plugin-persistedstate插件
})

组合式API写法中,state用ref/reactive定义,getters用computed定义,actions就是普通函数。这种写法的优势是类型完全自动推导,不需要像Vuex那样写额外的类型声明。

组件中使用Store与响应式解构

在Vue3组件中使用Pinia Store。注意直接解构会丢失响应性,需要使用storeToRefs:

<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()

// 使用storeToRefs解构state和getters保持响应性
const { userInfo, token, isLoggedIn, isAdmin, displayName, loading } = storeToRefs(userStore)

// actions可以直接解构,不需要storeToRefs
const { login, logout } = userStore

async function handleLogin() {
const success = await login('admin', 'password123')
if (success) {
console.log('登录成功', displayName.value)
}
}
</script>

<template>
<div>
<p v-if="isLoggedIn">欢迎, {{ displayName }}</p>
<button v-else @click="handleLogin" :disabled="loading">
{{ loading ? '登录中...' : '登录' }}
</button>
<button v-if="isLoggedIn" @click="logout">退出</button>
<p v-if="isAdmin">管理员权限</p>
</div>
</template>

多Store模块化组织与跨Store调用

大型项目中通常按功能拆分多个Store。例如拆分用户Store、购物车Store和设置Store:

// stores/cart.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useUserStore } from './user'

interface CartItem {
productId: number
name: string
price: number
quantity: number
}

export const useCartStore = defineStore('cart', () => {
const userStore = useUserStore() // 跨Store调用

const items = ref<CartItem[]>([])

const totalPrice = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)

const itemCount = computed(() =>
items.value.reduce((sum, item) => sum + item.quantity, 0)
)

function addToCart(product: Omit<CartItem, 'quantity'>) {
if (!userStore.isLoggedIn) {
throw new Error('请先登录')
}
const existing = items.value.find(i => i.productId === product.productId)
if (existing) {
existing.quantity++
} else {
items.value.push({ ...product, quantity: 1 })
}
}

function removeFromCart(productId: number) {
items.value = items.value.filter(i => i.productId !== productId)
}

function clearCart() {
items.value = []
}

return { items, totalPrice, itemCount, addToCart, removeFromCart, clearCart }
})

跨Store调用时直接在action中调用useUserStore()获取依赖的Store实例。Pinia内部处理了循环引用问题,无需担心初始化顺序。

Pinia持久化与SSR安全配置

安装pinia-plugin-persistedstate实现状态持久化:

npm install pinia-plugin-persistedstate

在main.ts中注册插件:

import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

在Store定义中添加persist配置:

export const useUserStore = defineStore('user', () => {
// ...省略store内容
}, {
persist: {
key: 'app-user',
storage: localStorage,
pick: ['token', 'userInfo'] // 只持久化这两个字段
}
})

对于SSR场景需要注意localStorage在服务端不存在。可以使用自定义storage适配器:

// utils/ssr-storage.ts
export const ssrStorage = {
getItem(key: string) {
if (typeof window === 'undefined') return null
return localStorage.getItem(key)
},
setItem(key: string, value: string) {
if (typeof window !== 'undefined') {
localStorage.setItem(key, value)
}
},
removeItem(key: string) {
if (typeof window !== 'undefined') {
localStorage.removeItem(key)
}
}
}

Pinia性能优化与重置状态实践

大型应用中Store数据可能很大,使用$reset和$patch批量更新可以优化性能:

// 批量更新避免多次触发响应式更新
userStore.$patch({
userInfo: newData,
token: newToken
})

// 重置state到初始值(仅Option API写法支持原生$reset,组合式API需手动实现)
function reset() {
userInfo.value = null
token.value = ''
loading.value = false
}

// 订阅state变化用于调试或持久化
userStore.$subscribe((mutation, state) => {
console.log('State changed:', mutation.type, mutation.storeId)
// 可以在这里做自定义持久化或埋点
})

// 订阅action执行结果
userStore.$onAction(({
name, after, onError
}) => {
console.log(`Action ${name} started`)
after((result) => {
console.log(`Action ${name} succeeded`, result)
})
onError((error) => {
console.error(`Action ${name} failed`, error)
})
})

$subscribe会在每次state变化时触发,适合做调试日志或自定义同步逻辑。$onAction可以拦截所有action的执行,适合做统一错误处理和性能监控。这些API在生产环境需要配合清理逻辑使用,在组件卸载时取消订阅避免内存泄漏。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/pinia-zhuang-tai-guan-li-yu-vue3-zu-he-shi-api-shi-zhan-pei/

(0)
小编小编
上一篇 17小时前
下一篇 17小时前

相关推荐