Pinia取代Vuex的核心优势
Vue3项目里Pinia已经成为状态管理的事实标准。与Vuex相比,Pinia去掉了mutation这个冗余层,store定义直接用函数返回值,TypeScript类型推导零配置,模块化天然支持tree-shaking。一个中型项目从Vuex迁移到Pinia,打包体积减少约40KB,代码量减少30%。这篇从架构设计到持久化缓存,覆盖Pinia生产级用法。
Store架构设计:按业务域拆分
不要把所有状态塞进一个store。按业务域拆分,每个store只管一个领域的状态和逻辑:
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const token = ref('')
const userInfo = reactive({
id: 0,
name: '',
role: '',
permissions: [] as string[]
})
const isLoggedIn = computed(() => !!token.value)
const hasPermission = (perm: string) => userInfo.permissions.includes(perm)
async function login(credentials: LoginForm) {
const res = await authApi.login(credentials)
token.value = res.token
Object.assign(userInfo, res.userInfo)
}
function logout() {
token.value = ''
Object.assign(userInfo, { id: 0, name: '', role: '', permissions: [] })
}
return { token, userInfo, isLoggedIn, hasPermission, login, logout }
})
组合式API风格(Setup Store)比选项式风格更灵活,可以直接组合composable函数:
// stores/cart.ts - 购物车store引用用户store
export const useCartStore = defineStore('cart', () => {
const userStore = useUserStore()
const items = ref<CartItem[]>([])
const totalPrice = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.count, 0)
)
// 跨store联动:未登录时只能浏览不能下单
function canCheckout() {
return userStore.isLoggedIn && items.value.length > 0
}
return { items, totalPrice, canCheckout }
})
异步Action与错误处理模式
Pinia的action支持async/await,配合请求拦截做统一错误处理:
// stores/product.ts
export const useProductStore = defineStore('product', () => {
const products = ref<Product[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
async function fetchProducts(categoryId: number) {
loading.value = true
error.value = null
try {
const res = await productApi.getList({ categoryId })
products.value = res.data
} catch (e: any) {
error.value = e.message || '商品加载失败'
// 上报到Sentry
captureException(e)
} finally {
loading.value = false
}
}
return { products, loading, error, fetchProducts }
})
组件里使用:
const productStore = useProductStore()
const { products, loading, error } = storeToRefs(productStore)
onMounted(() => productStore.fetchProducts(props.categoryId))
storeToRefs解构保持响应式。直接解构store对象会丢失响应式——这是新手最常踩的坑。
Pinia持久化:pinia-plugin-persistedstate配置
用户token、偏好设置等状态需要跨页面刷新持久化。用pinia-plugin-persistedstate插件:
// main.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
在store里声明持久化策略:
// stores/user.ts
export const useUserStore = defineStore('user', () => {
const token = ref('')
const theme = ref<'light' | 'dark'>('light')
const sidebarCollapsed = ref(false)
return { token, theme, sidebarCollapsed }
}, {
persist: {
// 只持久化部分状态
pick: ['token', 'theme', 'sidebarCollapsed'],
// 自定义存储引擎(默认localStorage)
storage: localStorage,
// key前缀避免冲突
key: 'app-user'
}
})
如果token是JWT这类敏感数据,用sessionStorage代替localStorage,标签页关闭自动清除:
persist: {
pick: ['token'],
storage: sessionStorage
}
自定义持久化引擎:接入IndexedDB
大数据量状态(如离线商品目录)存localStorage有5MB限制,需要接入IndexedDB:
// utils/idb-storage.ts
import { openDB } from 'idb'
const idbStorage = {
async getItem(key: string) {
const db = await openDB('pinia-store', 1, {
upgrade(db) {
db.createObjectStore('persist')
}
})
return db.get('persist', key)
},
async setItem(key: string, value: any) {
const db = await openDB('pinia-store', 1)
await db.put('persist', value, key)
},
async removeItem(key: string) {
const db = await openDB('pinia-store', 1)
await db.delete('persist', key)
}
}
// 使用
persist: {
storage: idbStorage,
pick: ['offlineProducts']
}
Store热更新与插件调试
开发环境启用Pinia Devtools和热更新:
// vite.config.ts
export default defineConfig({
plugins: [
vue(),
// Pinia热更新在Vite下自动支持
]
})
// 如果store定义在独立文件,确保HMR生效
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
}
Pinia Devtools面板可以直接查看store状态树、修改值、追踪action调用时间线。调试异步数据流时比console.log高效很多。
常见陷阱与解决方案
SSRhydration不匹配:服务端渲染时持久化数据导致首屏与服务端不一致。解决方案是在onMounted后再读取持久化数据,或使用ClientOnly组件包裹依赖持久化状态的部分。
循环引用导致内存泄漏:store之间互相引用形成循环时,组件卸载后store不释放。用computed做延迟计算而不是在setup里立即读取另一个store的状态。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-zhuang-tai-guan-li-pinia-jia-gou-she-ji/