Pinia替代Vuex的设计思路与核心优势
Pinia是Vue3官方推荐的状态管理库,相比Vuex简化了类型推导、去除了mutations层、支持Composition API风格定义store。Pinia的API设计更贴近Vue3的setup函数写法,开发者可以直接使用ref、computed等响应式API,无需在getters和mutations间来回切换。
Pinia与Vuex的核心差异:Pinia没有mutations概念,状态修改直接在actions中操作;Pinia的store是扁平结构,没有modules嵌套,每个store独立管理;Pinia对TypeScript的类型推导开箱即用,泛型配置更少。
Store定义与Composition API用法
使用Setup Store语法(Composition API风格)定义一个用户管理store:
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// state
const token = ref<string | null>(null)
const userInfo = ref<UserInfo | null>(null)
const permissions = ref<string[]>([])
// getters
const isLoggedIn = computed(() => !!token.value)
const hasPermission = computed(() => {
return (perm: string) => permissions.value.includes(perm)
})
const displayName = computed(() => {
return userInfo.value?.nickname || userInfo.value?.username || 'Guest'
})
// actions
async function login(credentials: LoginParams) {
const res = await authApi.login(credentials)
token.value = res.token
userInfo.value = res.user
permissions.value = res.permissions
localStorage.setItem('token', res.token)
}
function logout() {
token.value = null
userInfo.value = null
permissions.value = []
localStorage.removeItem('token')
}
async function fetchProfile() {
if (!token.value) return
const res = await authApi.getProfile()
userInfo.value = res
permissions.value = res.permissions
}
return {
token, userInfo, permissions,
isLoggedIn, hasPermission, displayName,
login, logout, fetchProfile
}
})
TypeScript类型定义与接口约束
为store定义完整的TypeScript类型,确保类型安全:
// types/user.ts
interface UserInfo {
id: number
username: string
nickname: string
avatar: string
email: string
role: 'admin' | 'editor' | 'viewer'
permissions: string[]
}
interface LoginParams {
username: string
password: string
captcha?: string
}
interface LoginResponse {
token: string
user: UserInfo
permissions: string[]
expiresIn: number
}
export type { UserInfo, LoginParams, LoginResponse }
在组件中使用store并保持类型推导:
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
// storeToRefs保持响应性,类型自动推导
const { isLoggedIn, displayName, userInfo } = storeToRefs(userStore)
// actions可以直接解构
const { login, logout } = userStore
// 调用时参数有类型检查
async function handleLogin() {
await login({
username: 'admin',
password: '123456'
})
}
</script>
多Store组合与跨Store调用
Pinia支持在一个store中调用另一个store,实现模块间状态共享:
// stores/cart.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { computed, ref } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
// 跨store调用
function checkout() {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
throw new Error('Need login first')
}
const order = {
userId: userStore.userInfo!.id,
items: items.value,
total: total.value
}
return orderApi.create(order)
}
return { items, total, checkout }
})
持久化插件集成
使用pinia-plugin-persistedstate实现store状态持久化到localStorage:
// main.ts
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPersist)
app.use(pinia)
// stores/user.ts - 在store定义中添加persist选项
export const useUserStore = defineStore('user', () => {
// ... store logic
return { /* ... */ }
}, {
persist: {
key: 'app-user',
storage: localStorage,
paths: ['token', 'userInfo', 'permissions']
}
})
persist.paths指定需要持久化的字段,避免将临时状态写入存储。对于敏感数据(如token),建议配合加密库进行加密后存储。
SSR同构与状态水合
Nuxt3或Vite SSR场景下,Pinia需要在服务端预取数据后序列化传输到客户端进行水合:
// 服务端入口
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
// 预取数据
const userStore = useUserStore(pinia)
await userStore.fetchProfile()
// 序列化state到HTML
const state = JSON.stringify(pinia.state.value)
// 客户端入口 - 水合
if (typeof window !== 'undefined') {
const pinia = createPinia()
pinia.state.value = JSON.parse(window.__PINIA_STATE__)
app.use(pinia)
}
SSR场景下避免在setup顶层直接调用store的async action,应在组件的onServerPrefetch或async setup中调用,确保服务端渲染时数据已就绪。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/pinia-zhuang-tai-guan-li-compositionapi-she-ji-yu/