vue3 使用有感
本文最后更新于:2022年9月19日 早上
假若没有看见光明,我本可以忍受黑暗。
场景
自上家公司从去年 5 月份开始成功推广 react 之后,很长一段时间吾辈一直在使用它,而今年,离职之后新的公司再次使用 vue3,因而见证了两个 team 踏入了同一条河流。不过 vue 作者说 vue3 使用 ts 重写,对其支持很好,吾辈姑且安心了一点,但经过近一个月的实践,吾辈还是发现了种种问题。
从 vue 迁移到 react 的原因参考: 2020 吾辈在公司推动的前端技术演进
基本感受
- vue3 的生态不稳定,而且仍然比较小
- vue3 和 ts 的结合仍然不算好
- webstorm/vscode 对 vue3+ts 的支持比较糟糕
vue3 的生态
vue 官方承认自身社区比 react 更小,但他们很明智的没有说出来,这究竟代表了什么。
- vue+ts 结合的体验很糟糕,下面会详述
- vue 的大版本升级虽然没有 react 那么快,但每次升级都是完全破坏性的,整个社区都要重新构建
- ant-design-vue 2 使用 vue3 重构,但使用体验仍然比不上官方的 ant-design(例如 Table 的能力还远远比不上 ant-design)
- antv 系列官方基本优先或仅支持 react,例如只有 react 版本的 G2Plot/Graphin
- 结合 storybook 有点恶心,需要在字符串中写模板
- vue-router 开发环境和生产环境的行为不一致,开发环境支持
import()
,生产环境则必须是() => import()
- 许多周边 npm 包都是 next/beta/rc 版本
- 异步组件比较奇葩,用了一种 map 的方式实现
vue+ts 结合
- 模板是个失败的设计,很多问题都是由模板衍生而来
- props 和 ts 结合仍有问题
- 模板中只能编写 js,但 vue-tsc 会使用 tsc 检查,这会导致一些奇怪的问题很难解决
- 使用 tsx 也会存在一些问题,但可能是现有条件下最好的解决方案(antdv 使用该方式),参考:极致的开发体验!Vite + Vue 3 + tsx 完整教程
vue 模板到底有什么问题?
相比于 vue 模板,jsx(或者说 react)的设计非常巧妙,它不需要 props/attrs/emits/slot/指令 这一系列 vue 的功能,而仅仅只需要 props,而它能够使得 dts 就能够完全支持 react 组件的接口定义,进而使编辑器能够通过 ts 做提示、导航和重构。而 vue 一时看起来方便开发者的设计,例如 props 支持定义类型、必填校验及 getter/setter,未在 props 声明的属性会被放到 attrs 并自动绑定到组件根元素上,前者在和 ts 结合时导致要重复定义 props 的类型,后者导致在支持 fragments 时反而需要手动指定绑定 attrs 的元素。
下面是一个 antdv List 组件的使用示例,如果不看文档,没人知道 slot 到底可以挂的是羊头,还是狗肉
1 |
|
vscode vetur 和 jetbrians(web-types) 为了解决开发体验的问题选择了两条不同的道路,但都存在一些问题,参考:web-types.json 目前仍然有问题,包括某些 slot/item 仍然是错误的,可以想见,既然连 antdv 这么流行的 vue ui 组件库都无法保证没有问题,更别提整个 vue 生态中的其他 ui 组件库了。
props 和 ts 结合还有什么问题?
props 在 vue3 的 ts 有所增强,至少能够使用 PropType
复杂类型了,例如下面
1 |
|
但它仍然存在一些问题
无法复用现有的类型。例如当我们已经有一个后端返回的数据结构类型了,而我们希望组件仅需要其中的部分字段,而这无法使用 ts 的类型操作完成。究其原因,还是因为 vue 的 props 定义仍然是值而非类型。
无法使用 ts 的可选属性。vue props 仍保留定义 required/default/validator
的功能,所以并不能使用 ts 的可选属性。下面的示例代码将上面限制表现的的淋漓尽致
1 |
|
无法使用泛型。最典型的莫过于 List 组件,我们希望传入的 data 数据决定 slot 中参数的类型,目前这是不可能的。下面是 react 中的泛型组件示例(本质上是泛型函数,对 state=>ui
的抽象是真的彻底)
1 |
|
模板中只能编写 js,但 vue-tsc 会使用 tsc 检查,这会导致一些奇怪的问题很难解决
模板里不能使用 ts,但 vue-tsc 却会检查 ts,然后就 GG 了
如上图,就因为模板中的函数参数没有指定类型导致 vue-tsc 报告错误了,但模板中也确实不能定义参数类型,所以目前只能将之放到 script 中并在 setup 函数返回(是不是感觉挺蠢的?)
以及模板中出现下面这个错误,也很奇葩(估计是粘合层 vue-tsc 没有修改 tsc 的类型检查器)
1 |
|
需要更换 vue-tsc 为 vls,配置 vite 插件 vite-plugin-checker
更新依赖
1 |
|
配置 vite.config.ts
1 |
|
如果遇到下面这个错误
1 |
|
请在 vite-env.d.ts 或 env.d.ts 中声明 vue 文件的类型定义
1 |
|
webstorm/vscode 对 vue3+ts 的支持比较糟糕
具体表现在提示、导航和重构。吾辈在想,是不是 vue 的开发者都习惯了这种问题,代码导航靠 C-F 一个个查找,重构时 CS-F 一个个手动替换(群友吐槽说:“不然怎样,就 vue 那个框架设计,完全不考虑静态分析好吧……估计反馈给 yyx,得到的回答是:这么爱静态分析,滚去用 angular”)
- 在 monorepo 中 vue-ts 提示很慢
- monorepo 中同时包含 react 子模块时,无法使用 vue tsx,webstorm 会默认为是 react tsx
- 重构不支持自动重命名 ref/setup 返回值/模板引用
一些感受到的优点
上面吐槽了那么多,vue 及其社区也并非没有可取之处
- vue3 的 hooks api 对心智负担确实更小,不太容易出现 react hooks 两个烦人的问题
- 强制要求依赖,但很容易错误(各种依赖引发的问题)
- 状态声明的方式独树一帜,与一般 js 隔离了。例如许多基于闭包实现的高阶函数都必须重写,但在 vue hooks 中则不用(好吧其实也需要,但至少能生效)
- vite/vuepress 很好用,比 snowpack/docusaurus 更好用(即便吾辈使用的是 react 技术栈,但仍然使用它们作为构建和文档工具)
其他
使用异步组件
参考:vue3 官方文档
全局注册所有异步组件(能够以编程的方式注册的方法)
1
2
3
4
5
6const components: Record<string, Lazy<Component>> = {
hello: () => import('hello'),
}
;[...Object.entries(components)].forEach(([k, v]) => {
app.component(k, defineAsyncComponent(v))
})通过
component
组件渲染1
<component is="hello" />
可以看到,基本上就是通过 map 的方式维护,要在局部引用之前还必须先在全局注册一下昭告天下 xd
更新
使用 defineAsyncComponent
包裹之后就像普通组件一样使用就好了
1 |
|
使用 provide/inject 模拟 react context
为什么要模拟 react context?
因为 vue3 仍然基于字符串实现的 provide/inject,而字符串可以任意写且不包含类型。
1 |
|
使用
1 |
|
关于这点,官方提出了自己的解决方案,参考: https://v3.cn.vuejs.org/api/composition-api.html#provide-inject
1 |
|
总结
上面只是吾辈的一些吐槽,但考虑到成本问题,在公司推广 react 短期来看仍然是不现实的(vue3、ts 甚至 esnext 都有人未能基本掌握)。