最近在做项目调优,一个 Vue3 项目当初使用 vuex 做的状态管理,最近大家都在谈 pinia,看了一下文档,它的使用方法更贴近于 Composition API,对 TypeScript 也更友好,写法上也更简洁,emmm...,香啊!
这必须安排啊,于是我开始用 pinia 替换 vuex。
- store 文件夹下的所有文件重构
- 项目中所有基于 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 加载 Vue
和 ElementPlus
,这里也说一下配置过程,方便需要的小伙伴。
// 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 插件,记得要安装一下。
到了这里,一切都很美好,打包上线吧!
项目部署之后,随便点一点测试下,然后发现当我点击这个按钮控制侧边栏展开收起的时候,
没有作用?!打开本地服务,点击,比德芙还丝滑好不好!那么问题就出在了线上环境和本地服务的差异。
因为线上环境每次都需要部署,比较麻烦(我是在个人服务器上部署了一套测试,所以不影响真实的线上环境),同时为了对比是不是线上环境的问题(Linux 服务器以及 nginx 配置),我在登录页面获取了 isCollapse
并尝试进行操作,同样,本地服务没有问题,打包,开启 Live Serve
,发现本地环境操作也不行,那么就可以确定是打包后的文件出了问题。
因为我项目配置了 cdn 加载 Vue
,所以自然想到了这个问题,把 cdn 加载干掉,再次打包,开启 Live Serve
,发现本地环境可以了。
到了这里,可以确定配置 cdn 加载后,造成了这个问题。因为实际项目依赖和模块比较多,为了排除干扰,调试更方便,我开了一个新的测试项目(我是之前就有这么一个专门用来测试问题的项目,用来复现和调试实际项目中的问题),首先把 Vue ElementPlus pinia
都搞好,不开启 cdn 加载,本地服务和打包后的本地环境都是 OK 的,然后开启 cdn 加载,本地服务 OK,打包后本地环境 pinia
响应式失效,完美复现。
接下来就是排查具体原因,有两个点:
- vite-plugin-externals
- pinia
查看 vite-plugin-externals
的 README
可以看到,它只是把依赖库的引入转为 window
下的全局变量,
// 选项 viteExternalsPlugin({ vue: 'Vue', }), // 源代码 import Vue from 'vue' // 转换后 const Vue = window['Vue'] 复制代码
并没有其他什么操作,所以我感觉不是它的问题,排除嫌疑。
那么就剩下 pinia
了,这个时候我打开打包后的 vendor.js 发现里面还是有 Vue 的代码。
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 了!
2022.3.13 更新
今天搭建新的开发框架遇到一个问题,vite-plugin-externals 之前我用的是 0.0.93 版本,这两天发布了 1.0.9 版本,今天默认安装也是这个版本,结果出现页面刷新后,再操作 pinia 状态,部分状态缓存失败问题,已提 issues,如果大家使用 1.0.0 版本出现问题,可以尝试使用 0.0.93 版本。
如有任何问题或建议,欢迎留言讨论!