本文是《Vue3 + TS 最佳实践 》的补充篇,笔者之所以“执着于”在 Vue 中使用 JSX,一方面是因为之前使用 React 形成的编码习惯。但更重要的是在使用 React v16.8+ 的过程中,深刻体会到 JSX 与 TS 的结合是最完美的,因为 JSX 本质上只是一种语法糖,并没有超出 JS 的范畴,自然与 TS 配合完美。
所以本文会聚焦于 Vue3 下 JSX + TS,即 TSX 的实现,来探索 TS 在 Vue3 下是否有另一种最佳实践。归根结底,还是为了 TS 的落地。
在笔者的另一个短篇文章《为什么 React 比 Vue 更适合集成 TS》(以下简称“短篇”)里,简单论述了以下内容:
- React 在结合 TS 方面为什么会这么成功
- JSX 在其中充当的角色
- JSX vs. Vue Template(SFC) 按照短篇中的逻辑,如果使用 JSX,就要放弃一些 Vue Template 带来的特性,比如一些自带的性能优化、状态驱动的动态 CSS 等。需要像 React 一样手动去做这些处理,这也就意味着有可能会降低整体工程质量的下限,当然好处就是更加顺滑的 TS 体验。对于这些点的取舍,还要同学们根据实际情况自己判断。毕竟软件开发本质上,是一项寻求平衡的妥协的艺术。
言归正传,先简单整理下 JSX 对 template 的优劣点:
优势
- 除了“标签语法”外,都是 JS 原生的语法,学习成本较小
- 同时意味着更灵活,可控性强,留给开发者全部的发挥空间
- 与 TS 完美结合
- 可以随着 ES、TS 的迭代升级自然进化,能力与开发体验都会不断提升
- 相较于 template 的 props、attrs、emits、slots 等概念,JSX 只有 props 一个概念,心智负担较小
劣势
- 部分人对于在 JS 里混入 HTML 语法表示很抗拒,感觉违背了逻辑与表现分离的原则
- 灵活意味着代码质量与程序员的水平强相关,也就是不好保证代码质量的下限
- 相应的,一些优化点需要程序员自己控制,比如 React 中的 useCallback、useMemo,相信能彻底弄明白的人不是多数
- 没有 template(SFC) 提供的 style 处理能力,如 scoped、module、状态驱动等
其实最关键的点就是如何把 template 中的特殊语法映射到 JSX 上,比如 slots、emits 等。这要靠查看官网 渲染函数 的说明来推测,下面我们用一个实际例子来具体看下怎么做。建议先看下本文的结论,心里先有个底。

- 分为 Parent 和 Child 两个组件。分别都会用 Vue3 + TS 最佳实践 和 TSX 的方式各实现一版。
- Parent,蓝色背景,组件内部维护一个 响应式变量,当点击 [Count++ 按钮] 时触发 方法自增,并动态展示在 Parent 中
- Child,黄色背景,组件的内部没有任何响应式的声明,只接收 Parent 传入的 、、
- Child 组件接收的 有 :count="count"、style; 有 #header 和 #default; 有 @childClick="handleIncrease"
- Child 会展示 以及 ,[Child Count++ 按钮] 的点击会触发 ,即触发 Parent count 的改变
为方便对比,这部分的代码用的图片,且做了对齐处理,文末会有文本代码,方便大家复制。
关键点如下:
- TSX 要用 包裹,并且只使用 (没有 data、methods、computed 等一级声明),返回值要是一个 ,里面采用 JSX 的写法;
- TSX(defineComponent) 中 、、 等的声明是省不了的, 的声明我们看下文 Child 的实现;
- 在 TSX 中要变为 ,自定义 也要由 变为 。不过这里要注意,正如“短篇”中提到的,如果用了 TSX,像 onClick 这种可能引起无效重复 render 的问题,就需要使用者自己解决了;
注:如果在 template 中同时传入 @childClick 和 :onChildClick 会发生什么呢?
答案是:不管传入顺序如何,:onChildClick 都会覆盖掉 @childClick,有兴趣的同学可以验证一下。所以在 template 中,事件还是老老实实的用 @ 的好。
- TSX 中的 ref 对象还是需要使用 结尾,有点麻烦,但是编辑器会自动补全;
- 如果有多个,TSX 要像例子中一样,通过一个对象传入子组件。对象的 key 为 slot 的名字,value 为要传入的组件;
综上,需要特别注意的就是 和 的特殊处理。另外,上例当中还存在一个比较大的问题,即 实际上会被编译器提示 TS 校验错误,但代码又是可运行的。要想解决这个问题,只能要求子组件不声明 emits,全部用 props.onXXX 代替,即放弃使用 (感觉不太合适)。这点现在是最难受的,笔者还没有想到一个好的解决方案。
上图中 TSX 的例子是用 Functional Component 形式实现的,这种组件使用 TSX 可谓是最舒服的,关键点如下:
- 注意两者 声明的不同,SFC 中参考官网的例子(仅限类型的 props/emit 声明),TSX 中 Emit 的声明一定要是 格式而不能是 ,这是由 内部泛型处理逻辑决定的;
- TSX 中可以使用解构赋值,这是一个组件二次封装的场景下很常用的一个语法;
- 在 TSX 中以函数的形式调用,注意例子中的容错写法,防止没有 slot 传入时的报错; 综上,在 Functional Componet 的场景下,选择 TSX 是个不错的决定。如果用 的方式实现 Child 应该是什么样呢?也顺便解答一下上面留下的一个疑问,关于 props 声明的问题。
关键点如下:
- 要想 有很好的提示,必须要按照上例的方式,用 来声明各种属性的类型;
- 目前没有找到很好的 TS 声明的方法。即使用验证模式去写,也很不理想,毕竟我们要声明的是 emit 本身,而不是它的验证函数,意义都变了; 只能说太尴尬了,如果 TS 是这么使用的话,实在是太别扭了。也许对 进行 TS 加强后,能够一定程度的优化这个问题吧。
本文通过一个典型的父子组件场景,来模拟实践中可能遇到的各种情况,最终的结论如下:
- Functional Component(即只有 、、 传入的组件),可以完美的使用 TSX;
- 没有 和 传入的组件(如本文的 Parent 组件),使用 TSX 还算可以接受,但是实际上组件内部没有太多的利用上 TS;
- 普通组件,尤其是带了 和 的组件,用 TSX 形式实在是有点强人所难; 所以想要愉快的在 Vue3 中使用 TS,还是首选 Vue3 + TS 最佳实践 的方式吧。如果项目解耦的特别好,有大量的 Functional Component,可以考虑用 TSX。不过 Vue3 官网有这样一句话:
- 在 3.x 中,2.x 带来的函数式组件的性能提升可以忽略不计,因此我们建议只使用有状态的组件
好吧,笔者已经找不出其他理由使用 TSX 了(除了 React 带来的惯性)。所以,Vue3 + TSX 可以说目前还不存在足够“佳”的实践,更不用说是最佳实践了。否定的结论也是结论,最后希望整个问题的思考过程,也能够带给大家一些收获吧。
"I have not failed. I've just found 10,000 ways that won't work." —— Thomas A. Edison
- 尤雨溪vue 3.0直播你学到了什么?
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/11532.html