pinia 踩坑总结

9,802 阅读4分钟

最近在做项目调优,一个 Vue3 项目当初使用 vuex 做的状态管理,最近大家都在谈 pinia,看了一下文档,它的使用方法更贴近于 Composition API,对 TypeScript 也更友好,写法上也更简洁,emmm...,香啊!

真香.jpeg

这必须安排啊,于是我开始用 pinia 替换 vuex

  1. store 文件夹下的所有文件重构
  2. 项目中所有基于 vuex 的状态替换为基于 pinia 的状态

= 获取状态响应式丢失

然后遇到第一个问题,如果要获取某个状态,并进行一些操作,最好不要这么写

import { useSettingStore } from "@/store/setting";
const settingStore = useSettingStore();
const isCollapse = settingStore.isCollapse;
const changeCollapse = () => {
  settingStore.isCollapse = !isCollapse;
};

这样直接 const isCollapse = settingStore.isCollapse; 通过 = 赋值拿到的状态是没有响应式的,正确的方式是通过 storeToRefs 进行解构获取,修改后代码如下:

import { storeToRefs } from "pinia";
import { useSettingStore } from "@/store/setting";
const settingStore = useSettingStore();
const { isCollapse } = storeToRefs(settingStore);
const changeCollapse = () => {
  settingStore.setCollapse(!isCollapse.value);
};

ps: 修改状态的时候最好还是通过 action 来做。

cdn 加载 Vue 线上环境 pinia 响应式丢失

这个坑真的是如果思路不对,要被坑死。
为了项目更快的加载,我配置了 external 通过 cdn 加载 VueElementPlus,这里也说一下配置过程,方便需要的小伙伴。

// vite.config.js
import { viteExternalsPlugin } from "vite-plugin-externals";
plugins: [
    // external cdn 引入依赖包
    viteExternalsPlugin(
        {
          vue: "Vue",
          "element-plus": "ElementPlus",
        }
    )
]
// index.html
<head>
    <meta ...
    <!-- 导入 Vue 3 -->
    <script src="//unpkg.com/vue@next"></script>
    <!-- 导入样式 -->
    <link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" />
    <!-- 导入组件库 -->
    <script src="//unpkg.com/element-plus"></script>
    <title>...
<head>

这样就完成了,这里用到了 vite-plugin-externals 插件,记得要安装一下。

到了这里,一切都很美好,打包上线吧!

项目部署之后,随便点一点测试下,然后发现当我点击这个按钮控制侧边栏展开收起的时候,

image.png

image.png

没有作用?!打开本地服务,点击,比德芙还丝滑好不好!那么问题就出在了线上环境和本地服务的差异。
因为线上环境每次都需要部署,比较麻烦(我是在个人服务器上部署了一套测试,所以不影响真实的线上环境),同时为了对比是不是线上环境的问题(Linux 服务器以及 nginx 配置),我在登录页面获取了 isCollapse 并尝试进行操作,同样,本地服务没有问题,打包,开启 Live Serve,发现本地环境操作也不行,那么就可以确定是打包后的文件出了问题。
因为我项目配置了 cdn 加载 Vue,所以自然想到了这个问题,把 cdn 加载干掉,再次打包,开启 Live Serve,发现本地环境可以了。
到了这里,可以确定配置 cdn 加载后,造成了这个问题。因为实际项目依赖和模块比较多,为了排除干扰,调试更方便,我开了一个新的测试项目(我是之前就有这么一个专门用来测试问题的项目,用来复现和调试实际项目中的问题),首先把 Vue ElementPlus pinia 都搞好,不开启 cdn 加载,本地服务和打包后的本地环境都是 OK 的,然后开启 cdn 加载,本地服务 OK,打包后本地环境 pinia 响应式失效,完美复现。
接下来就是排查具体原因,有两个点:

  1. vite-plugin-externals
  2. pinia 查看 vite-plugin-externalsREADME 可以看到,它只是把依赖库的引入转为 window 下的全局变量,
// 选项
viteExternalsPlugin({
  vue: 'Vue',
}),
// 源代码
import Vue from 'vue'
// 转换后
const Vue = window['Vue']

并没有其他什么操作,所以我感觉不是它的问题,排除嫌疑。
那么就剩下 pinia 了,这个时候我打开打包后的 vendor.js 发现里面还是有 Vue 的代码。

image.png

vue hack 数组

然后我去看了下 pinia 的源码,发现它依赖了 vue-demi 这个库,而 vue-demi 这个库又依赖了 Vue ,而我们配置 external 的插件 vite-plugin-externals 默认是不处理 node_modules 下面的包的,所以我们使用的 Vue 实例和 pinia 绑定的 Vue 实例不是一个实例,所以我们操作 pinia 中的状态在项目中表现为没有响应。
所幸 vite-plugin-externals 提供配置,可以帮我们处理 node_modules 下面的包,所以修改配置如下:

// vite.config.js
import { viteExternalsPlugin } from "vite-plugin-externals";
plugins: [
    // external cdn 引入依赖包
    viteExternalsPlugin(
        {
          vue: "Vue",
          "element-plus": "ElementPlus",
        },
        {
          filter(code, id) {
            // 处理 pinia,解决 cdn 加载 Vue 响应式丢失问题
            if (id.includes("pinia")) {
              return true;
            }
            return false;
          },
        }
]

这样 vite-plugin-externals 就会帮我们把 pinia 中依赖的 Vue 也处理成 window['Vue']

再次打包测试,本地环境以及部署线上环境就都 OK 了!

nice老爷爷.gif

如有任何问题或建议,欢迎留言讨论!