背景
前段时间需要排Q3的前端技术项,刚好我一直想做vue3项目很久了,就与领导商议了一下,把一个线上小项目改造成vue3版本的。
这是个PC端项目,作为微前端被内嵌至别的应用之中,项目不大,只有三个界面,不过这三个界面都是基础核心业务模块,里面内嵌了树、动态布局、动态筛选等N个业务组件。
在最近一个月内,经过断断续续的开发,昨天项目发布到生产环境。现总结一下这段实践。
技术栈
- vue全家桶:
vue3.2、vue-router4.0、pinia2.0
- 组件库:
element-plus 2.2.x、vxetable 4.2.x
- 加强类:
typescript、eslint、prettier
- 打包构建:
vite 2.9.x
- 微前端相关:
vite-plugin-qiankun
之所以在使用element plus
的基础上,又添加了vxetable
的使用,是因为之前的项目表格都是使用的vxetable
,后者在复杂表格的实践上,功能是远超过element table
的
具体开发过程
- 使用的
npm init vue@latest
脚手架初始化工程 - 删除无用代码,引入组件库、axios、sortable等业务包
- 业务界面、业务组件通过组合式api全部重写(之前的代码已经成了巨石应用)
- 全局组件部分使用组合式api重写
- 微前端整合
- 代码优化、注释,上线
ts的引入
之前开发react的时候,使用过ts,不过开发vue2项目的时候,由于其对ts支持的不友好,都没有使用ts
由于vue3对ts支持的比较好,再加上ts目前作为前端开发届的标配,没有理解不添加使用ts
选择组合式api(Composition API
)
个人觉得是vue3相对于vue2开发体验改变最大的地方,完全颠覆了之前的option写法。
作为当年第一波吃vue2螃蟹人,我对option写法很是喜欢,并且制定了一系列规范来写更好的option,如data里面的数据,按模块顺序、分开书写
、vue2 的option属性需要按顺序排列,不能留空
(详见:前端规范 - vue2开发规范)
在之前的vue2项目中,单vue组件过大的话,的确会出现上下横跳的费劲场景,一个业务逻辑需要在data、watch、methods中来回书写
使用组合式api的确改变了开发体验。上图可以很形象的总结出变化,以后写代码,单vue组件内比较容易划分模块
而组合式api代理的所有模块都是import
导入这点,与react又是一大相似点
setup语法糖
相对比第一波吃vue3螃蟹的人,我入场的时候已经支持了setup语法糖,那没理由不使用它
组合式函数来代替mixin
组合式函数,因为与react hooks
的思路类似,业内也叫vue hooks
个人对vue2的mixin是又爱又恨,在拆分巨型vue文件时,mixin往往是利器,但其自身的缺陷(数据来源不明确、数据覆盖等),又让我不敢随意使用mixin
组合式函数的出现,就是为了替代mixin;在实际的开发中,全局、模块内也是创建hooks文件夹
,里面放js文件
但hooks也不是100%的好,因为其传参、返回都是显式定义,因此会对上下文顺序有依赖
provide、inject来做全局应用
在vue2的时候,我推荐将http请求、url链接统一归纳管理,然后挂载在vue prototype
上,就可以直接通过this进行调用,如下面的代码
this.$http.get(this.$listUrl.bom.getList, ...)
现在就不行了,因为组合式api直接没有this了!
替代方案1:每个界面都引用 $http
、$listUrl
,这个属于下策
替代方案2:在main.ts
定义全局变量,然后每个vue组件通过getCurrentInstance
获取调用,如下面代码
这种跟微信小程序的获取全局变量很是类似,但是不好用,引用的代码很多,vue官方也不建议使用
// mian.ts中定义全局变量
app.config.globalProperties.$http = $http
// 业务vue中
import { getCurrentInstance } from 'vue'
const { ctx } = getCurrentInstance() as any
ctx.$http.get
代替防范3:使用provide、inject
来做全局应用
在main.js定义全局变量,然后每个vue组件通过inject获取,如下面代码
虽然不如vue2中使用的便捷,不过也容易理解
// mian.ts provide全局变量
app.provide('global', {
$http,
$listUrl
})
// 业务vue
import { inject } from 'vue'
const global = inject('global')
global.$http.get(global.$listUrl.MATERIAL_SELECT_TREE_INFO, { params }).
对vue2代码的兼容
很多项目无法升级到vue3的一大原因是因为vue3的周边生态不够完善,尤其是公司内部的生态支持
我这次升级的业务使用到了两个复杂的业务组件,直接通过npm引入是不行的,只能拿到本地来调整
vue3也支持之前的option写法,不过个别api需要做出改动,我遇到的改动不算很多,具体如下:
- emit需要显式定义好
- 自定义v-model的改动
- 生命周期的改动
那些小坑你一把的改动
v-model 的改动,vue2是value,现在是modelValue
prop: value -> modelValue;
event: input -> update:modelValue;
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
<ChildComponent v-model="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
外部调用组件内部方法、属性,需要显式抛出
否则调用不到
// 子组件
// 定义方法
function open() {...}
// 显式抛出
defineExpose({
open
})
那些废弃了的api
eventBus
这个还好,本身算是老古董,我也一直不建议使用filter
这个我很难过,从我接触vue1.x
版本开始,过滤器就是我喜欢vue的一个特性,现在废弃了有点舍不得
element plus的升级
作为vue3的对应版本,能感受到element plus
的努力
- icon引入方式的变化 - 这个影响最大
- size取消了mini的规格
- button type=“link”的弃用
- treeV2 树的虚拟滚动添加 - 有小坑需要踩
- tableV2 表格的虚拟滚动添加 - 没具体使用,估计也会有小坑
构建工具选择vite
这算是开发过程中体验最舒服的地方了,启动秒起
、代码修改后界面秒刷新
默认打包命令、文件夹与之前的vue cli
默认一致,可以无缝对接公司的devops
系统
不好处:
由于vite使用rollup来打包,因此你需要做一些配置的话,需要查阅rollup的api文档,相当于你当年在webpack踩的坑,现在又需要重新走一遍
Vuex -> Pinia机会可以忽略的改动
我算是个vuex的保守使用者,在不需要共享数据的时候,我是不建议使用vuex的
这次项目改动使用的Pinia,属于常规操作并且地方不多,除了Pinia删除了mutations
这个小改动外,没感觉到有多大的变化
cdn的引入
vite是支持静态js通过cdn来引用的,这样会减少加载包的大小
社区有很多解决方案,都是通过rollup的扩展能力
不过我使用cdn引入,与微前端的模式有冲突,后面放弃了cdn的引用
<!-- index.html界面引用cdn文件(要引公司内部的cdn,不能引用第三方cdn) -->
<script src="//xxcdn/vue/3.2.37/vue.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/vue-demi@0.13.7"></script>
...
// vite.config.ts
// 需要install下面的两个包
import commonjs from 'rollup-plugin-commonjs'
import externalGlobals from 'rollup-plugin-external-globals'
...
const globals = externalGlobals({
vue: 'Vue',
'vue-router': 'VueRouter',
'vue-demi': 'VueDemi',
pinia: 'Pinia',
axios: 'axios',
'element-plus': 'ElementPlus',
'@element-plus/icons-vue': 'ElementPlusIconsVue',
})
const plugins = process.env.NODE_ENV === 'production' ? [] : [commonjs(), globals]
defineConfig({
plugins: [vue(), ...plugins],
build: {
target: 'es2015',
rollupOptions: {
external: ['vue', 'vue-demi', 'pinia', 'vue-router', 'element-plus', '@element-plus/icons-vue'],
plugins: [
commonjs(),
globals
],
},
},
微前端的适配
一早就知道qiankun
的官网不支持vite,但又舍不得vite的开发体验,一开始想做成本地vite+打包使用webpack的模式,
不过还是在社区里面找到了解决方案,通过vite-plugin-qiankun
来实现,虽然最后无法实现动态插入publicPath
,不过瑕不掩瑜,完全不影响生产环境使用
其他开发体验
- 使用
proxy
代替了Object.defineProperty
做数据绑定这点,对开发体验小有提升,不需要再写$set
、数组splice
模拟通过下标修改 Volar
不是那么的好用,无法进行详细的配置,格式化的时候对组件多属性的折叠不友好,这里推荐使用prettier
做代码格式化;而且- vsode开发体验,不知是个人vscode的问题,感觉
代码提示、输入反馈
,不如vue2项目的体验来的好,后续需要持续关注
vue devtools
升级到了新版本,体验倒是不错
性能提升
说实话,由于数据量不是十分大,直观体验上,性能没有多少提升……后续需要找一下经典场景来测试一下
总结
这个项目也算是完整的从0到1开发一个vue3项目,虽然项目不算大,但麻雀虽小五脏俱全,该有的体验也都把玩了一下。
总体来说,vue3带来了一些新内容,但还是属于框架内的改动,无法影响到整个前端圈。如果是之前平稳运行的项目,不建议推倒重构
到vue3版本,新项目或者小项目重构,可以大胆使用vue3
vue3是vue的未来,现在也已经很成熟了,后续的新项目,都推荐使用vue3来做的
后续
近期需要根据此项目,抽出脚手架来,供团队内使用。
还需要多学习总结,打造vue3版本的代码规范+最佳实践,以及ts的代码规范