Pinia实战总结与Vue3状态管理的那些坑
我的写法,亲测靠谱
先说说我用 Pinia 的习惯吧。说实话,刚从 Vuex 转过来的时候,我还真有点不适应。但用了几个项目之后,发现 Pinia 真香。我一般会在 src/stores 文件夹下按功能模块划分 store,比如 user.js、cart.js 这样。
// src/stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
token: localStorage.getItem('token') || ''
}),
actions: {
setUserInfo(data) {
this.userInfo = data
},
login(token) {
this.token = token
localStorage.setItem('token', token)
},
logout() {
this.token = ''
this.userInfo = null
localStorage.removeItem('token')
}
}
})
这种写法有几个好处:第一,逻辑清晰,每个 store 只处理自己的事情;第二,持久化数据可以直接在 state 初始化时处理,比如上面代码里的 token。不过这里要提醒一句:千万别在 state 里直接操作 localStorage,我之前就这样写过,结果在 SSR 项目里直接报错,因为服务端根本没有 window 对象。
这几种错误写法,别再踩坑了
来说说几个常见的坑吧。首先是有人喜欢在组件里直接修改 store 的 state,像这样:
// 错误示范
setup() {
const userStore = useUserStore()
userStore.userInfo = { name: '张三' } // 直接修改state
}
这种写法看着方便,但问题很大。首先破坏了数据流的可追踪性,后面维护起来想骂人。其次如果涉及到复杂的数据处理或异步操作,这种方式根本没法处理。建议还是老老实实写 action。
第二个坑是滥用 getters。很多人把所有计算属性都往 getters 里塞,导致 store 变得臃肿不堪。我建议只把那些需要复用的计算逻辑放到 getters 里,简单的计算直接在组件里处理就行。
// 不推荐
getters: {
fullName: (state) => ${state.firstName} ${state.lastName},
ageGroup: (state) => state.age > 18 ? 'adult' : 'minor'
}
// 推荐
getters: {
// 只保留确实需要复用的复杂计算
permissions: (state) => {
return state.roles.map(role => role.permissions).flat()
}
}
实际项目中的坑
最近在做个项目时遇到个有意思的问题。我们有个订单状态需要在多个页面共享,刚开始图省事直接用了一个全局 store。结果发现不同页面对订单数据的需求差别很大,有的要实时更新,有的只要初始值就够了。
折腾了半天才发现,这种场景其实更适合用两个 store:一个负责实时状态,一个负责基础数据。后来我改成了这样:
// 实时状态store
export const useOrderStatusStore = defineStore('orderStatus', {
state: () => ({ status: 'pending' }),
actions: {
updateStatus(newStatus) {
this.status = newStatus
}
}
})
// 基础数据store
export const useOrderInfoStore = defineStore('orderInfo', {
state: () => ({ orderId: null, createTime: '' }),
actions: {
setOrderInfo(data) {
Object.assign(this, data)
}
}
})
还有一个坑是关于插件的。有次我想实现数据持久化,直接用了 pinia-plugin-persistedstate,结果发现有些敏感数据也被自动存到 localStorage 了。最后我改成手动控制持久化的字段:
export const useAuthStore = defineStore('auth', {
state: () => ({
token: '',
permissions: []
}),
persist: {
paths: ['token'] // 只持久化token
}
})
其他一些小经验
说说调试的事儿。Pinia 的 devtools 支持真的很棒,但我发现很多新手不知道怎么用。其实只要在创建 store 时加上个选项就行了:
export const useSomeStore = defineStore('some', {
// ...其他配置
devtools: true
})
`>
<p>还有就是关于 TypeScript 的使用。虽然 Pinia 对 TS 支持很好,但别太依赖类型推导。我习惯给 state 和 actions 都明确标注类型,特别是当项目变大之后,这能省不少事:</p></code></pre>typescript
interface UserState {
id: number
name: string
isAdmin: boolean
}
export const useUserStore = defineStore('user', {
state: (): UserState => ({
id: 0,
name: '',
isAdmin: false
}),
actions: {
setUser(payload: Partial<UserState>) {
Object.assign(this, payload)
}
}
})
`>
以上是我总结的最佳实践
用了这么多项目下来,我觉得 Pinia 真的是个很实用的状态管理工具。它不像 Vuex 那么重,但该有的功能都有。当然,这些只是我个人的经验,肯定还有改进空间。如果你有更好的方案,欢迎在评论区交流。
对了,最近在研究 Pinia 和 Vue Router 的配合使用,感觉也有不少门道,等我整理好经验再来分享。希望这篇文章能帮你少踩几个坑,项目顺利上线!

暂无评论