Pinia实战总结与Vue3状态管理的那些坑

Zz亚捷 框架 阅读 2,175
赞 1 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

先说说我用 Pinia 的习惯吧。说实话,刚从 Vuex 转过来的时候,我还真有点不适应。但用了几个项目之后,发现 Pinia 真香。我一般会在 src/stores 文件夹下按功能模块划分 store,比如 user.js、cart.js 这样。

Pinia实战总结与Vue3状态管理的那些坑

// 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
})
`>

&lt;p&gt;还有就是关于 TypeScript 的使用。虽然 Pinia 对 TS 支持很好,但别太依赖类型推导。我习惯给 state 和 actions 都明确标注类型,特别是当项目变大之后,这能省不少事:&lt;/p&gt;</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 的配合使用,感觉也有不少门道,等我整理好经验再来分享。希望这篇文章能帮你少踩几个坑,项目顺利上线!

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论