各位同学下午好,我是来自字节跳动大力智能前端团队的林成璋,最近半年的业余时间(再加上一些摸鱼的时间)主要在维护 Vue 3 的 Babel JSX Plugin[1],今天来给大家做一个关于 JSX 的分享。
下面是我的 Github 账号,全网除了 P 站应该都是这个头像。其实最早做这个插件主要是为了帮助 Ant Design Vue 和 Vant 能够快速升级到 Vue 3,看过他们源码的同学应该知道,他们的源码大部分都是用 JSX 来撸的。
虽然目前在 NPM 上的周下载量是 56 万多(甚至超过了 Vue 3 ),但是这里的下载量非常大的原因主要是通过 vue-cli 创建的项目(不管是 Vue 2 还是 Vue 3)都会下载 @vue/babel-plugin-jsx 这个包,实际使用 JSX 的用户应该远比这个数字要小,到底有多少用户是通过的 JSX 的方式开发的也没有办法统计到,绝大用户还是使用 template 的开发方式为主。
在 Vue 里,sfc 是一个以 .vue 结尾的文件,通常包含三种类型的顶级语言块 <template>
、<script>
和 <style>
,可以理解为 HTML 、JS 以及 CSS 的组合。每一个 .vue 文件结尾的文件都是一个组件,而且只能 export defualt 出一个组件。
本身就是 JS
Vue 官方推荐的开发方式是 template,从 Vue 2 开始,template 在运行之前,会被编译成 JavaScript 的 render function。这些 render function 在运行时阶段,就是传说中的 Virtual DOM。
每当讲到 template 和 JSX,可能就会讨论到一个比较大的问题,React 和 Vue 哪个好。一些人可能就不太喜欢通过 JavaScript 直接来表示 UI,然而也会有相当一部分人会认为用 template 来写可能比较烦,特别是 React 资深玩家。由于 vue 是全球最友好的 UI 框架,有广大的群众基础,一些群众习惯于直接用 HTML 和 CSS 来干代码,对他们来说,把写 UI 的逻辑从 HTML 转到 template ,比让他们的思路完全变更到开始思考如何用 JavaScript 来构建 UI 要简单得多。但是也不得不承认,对于一些之前是搞后端的同学, 或者 iOS 和 Android 的开发者来说,之前没有怎么接触过 HTML 的,通过字符串模板的方式来编写 UI 也不太行。
不同用户的口味不太一样,萝卜白菜,各有所爱。就像我这张 PPT,有些人看了可能很兴奋,一些人可能觉得我是个傻 X。你可以说一堆模板怎么怎么不好的例子,他也同样也给 JSX 一顿喷,谁也说服不了谁。所以 Vue 干脆把两个事都干了。
JSX[2] 最早是由 facebook 起草的一个规范,后面的这个 X 可以理解为它是 JavaScript 的语法扩展,感兴趣的同学可以从这个链接进去看看里面的具体内容。由于各个前端框架的实现不一样,所以它不会由引擎或浏览器实现,需要 Transform 之后转成常规的 JS 之后,这一步操作我们可以理解为「赋能」,才能在浏览器里面运行。JSX 其实也和模板语言类似,但它具有 JavaScript 的全部功能,但是由于在模板中的一些限制,用模板写出来的代码性能要比 JSX 好得多。
<h1>Hello, world!</h1>;
这里的 JSX 语法编译之后其实就是:
import { createVNode as _createVNode } from "vue"
_createVNode("h1", null, "Hello, world!");
Vue 2 早期是用纯 JavaScript 来编写的,随着项目越来越庞大,引入了 Facebook 的 Flow[3]。虽然 Flow 在一定程度上起到了帮助作用,但还是存在一些问题,尤大也曾经公开表示当初没有选择 TypeScript 选择了 Flow 是「押错宝」了。
在 Vue 2 中,JSX 的编译需要依赖 @vue/babel-preset-jsx 和 @vue/babel-helper-vue-jsx-merge-props 这两个包。前面这个包来负责编译 JSX 的语法,后面的包用来引入运行时的 mergeProps 函数。
但是如果你要用 TSX 的环境来写,还需要额外安装 vue-tsx-support[4]。
在 Vue 3 中,只要安装一个 Babel 插件就完事了,可以理解为不再需要额外的第三方库,源码中就有 jsx.d.ts[5] 用来支持 JSX 的类型检查
我们现在来看下有哪些场景用 JSX 会比模板更加优雅。
一个 .vue 文件里面只能写一个组件,这个说实话在一些场景下还是不太方便,很多时候我们写一个页面的时候其实可能会需要把一些小的节点片段拆分到小组件里面进行复用,这些小组件其实写个简单的函数组件就能搞定了。如果你现在没有这个习惯可能就是因为 SFC 的限制让你习惯了全部写在一个文件里面。
比如这里我们封装了一个 Input 组件,我们希望同时导出 Password 组件和 Textarea 组件来方便用户根据实际需求使用,而这两个组件本身内部就是用的 Input 组件,只是定制了一些 props。在 JSX 里面就很方便,写个简单的函数组件基本上就够用了,通过 interface 来声明 props 就好了。但是如果是用模板来写,可能就要给拆成三个文件,或许还要再加一个 index.js 的入口文件来导出三个组件,摸鱼的时间又少了。
模板中引用了一个未在 script 中声明的 a,vscode 插件可以帮忙检查出来,但是仍然可以跑起来。
如果是用 TS 来写,这里引用了一个未声明的 c 变量,TS 在编译阶段就能让代码直接跑不起来。目前模板还是会被直接编译成 JS,因此还做不到在 template 就进行编译时的类型检查。
由于 JSX 的本质就是 JavaScript,所以它具有 JavaScript 的完全编程能力。举个例子,我们需要通过一段逻辑来对一组 DOM 节点做一次 reverse,如果在模板里面写,那估计要写两段代码。虽然这个例子可能不太常见,但是不得不否认,在一些场景下,JSX 还是要比模板写起来更加顺手。
在模板里面,由于一些历史的原因,目前范型组件确实还支持不了,但是不代表以后不行。如果非要用范型,可以先用函数组件给包一层,但是注意不要声明 FunctionalComponent 的类型。这里我们在 .tsx 文件里面声明 Foo 组件,Props 是一个范型。声明完之后,再回到模板里面,可以我们看到,刚刚定义的范型组件已经生效了。SFC 的 TS IDE 支持可以用 volar。volar 还支持了范型组件,用起来感觉和 TSX 已经没多大区别了。
在模板中,对 props 的处理是 merge。为了满足不同用户的需求,开了一个可以覆盖的口子。
插槽是一种内容分发(content distribution)的 API,洋文叫 Slot,也就是 createVNode 的最后一个参数。适合用在结果比较复杂,组件内容可以复用的地方,简单来说就是在组件中可以预留空间,从父级把内容给传进去。在 JSX 中,父组件给子组件来传递 VNode 通过属性来传递就完事了。
但是在模板中,传递属性的时候,template 里面是不能写 VNode 的,因此 Vue 里出现了插槽这个概念,插槽只在组件的 children 里面才有。我们来看下 Vue 是怎么处理插槽的:
Vue 对插槽的要求最好是一个 function,对运行时的性能提升会有很大的帮助。因此 A 组件的子节点会被编译成,{ default: () => [123] }。对应到 JSX 中,按照正常用户的心智模式,只有一个 children 的时候,写成{ default: () => [123] }也不太现实,正常的写法就是直接塞一个 children。但是在编译阶段要处理成 function,否则在开发时会报 warning,对开发者来说是非常不友好的体验。对编译器来说其实也好办,给子节点的 VNode 包一层函数就完事了。
在多个插槽的情况下,稍微比单个的场景要复杂点。除了 default 之外的插槽,通过 props 的方式来传是不可能的,只能想办法通过类似「指令」的方式来传递,因此最早设计了 v-slots 的命令来处理插槽。但是 v-slots 对于一些开发者来说可能会不够直观。更直观的方式应该像这样,也就是 obejct slots:
先简单讲一下两个概念:编译和运行时。编译就是把我们的代码转成 JavaScript 引擎可以看懂的代码,运行时就是 JavaScript 引擎开始跑你的代码。就好比我们招聘中的简历筛选和面试,简历筛选可以对应编译,面试来运行时。这个候选人到底怎么样,单纯看简历是看不出来的。再回到刚刚的问题,如果直接把 children 写成一个内联的对象还好办,但如果是一个变量的话,在编译的时候,编译器是无法知道传过来的到底是个什么玩意儿,是 slots 还是 VNode 其实编译的时候看不出来。如果是一个文件里面的,编译器或许还能判断,但是从另一个文件 import 进来,是无法判断的。Babel 处理每一个的文件都是一个「闭环」 。所以这时候就需要加一个运行时的判断:
虽然解决了判断是不是 slots 的问题,但是每一个变量给加上运行时判断,会对编译产物的体积有一些影响。jsx-next #255
为了保持编译产物体积和直观语义上的平衡,就让开发自己来选择是否需要上述的 feature,提供了 enableObjectSlots 的开关。
刚刚说了一些在哪些场景下用 JSX 可能会更加地合适。这里简单地对比了下实现相同功能,JSX 和模板的性能差异。左右两个 demo 里面,整了两万个节点,奇数节点里面 class 是动态的,偶数节点的 textContent 是动态的,点击 shuffle。在这个例子里面,用模板写的代码 比用 JSX 写的要快十几毫秒。在实际的场景中,组件的层级嵌套远比这里给出的 demo 要复杂,这个时候就更加能够体现模板的优势了。
在传统的 VDOM 树中,我们在运行时不能够得到用于优化的信息。在 Vue 3 中,充分利用了模板静态信息,最终体现到 VDOM 树上。比方说在 diff 的时候,可以知道哪些节点是动态的,节点的哪些属性是动态的。有了这些信息我们就可以在创建 VNode 的时候来标记哪些属性是不是动态的(靶向更新),也就是传说中 PatchFlags。除了 PatchFlags 之外,Vue 3 的 VDOM 在运行时,还做了一些缓存,比如 children 的缓存。
先来解释一下 PatchFlags 是怎么运作的,其实它就是一个数字,只不过在运行的时候被赋予了不同的含义:
可能一些同学不太明白这样来表示有啥好处 CLASS = 1 << 1
,这其实就是用二进制来表示,在上面的代码中:
TEXT = 0000000001
CLASS = 0000000010
STYLE = 0000000100
比如一个节点的 class 和 style 都是动态的,就给标记上 PatchFlags.CLASS | PatchFlags.STYLE,得到 0000000011 。想要判断它的 TEXT 是不是动态的,只需要 FLAG & TEXT > 0 就行。这么看起来只要把 props 的属性做标记好像 JSX 里面也能对 VDOM 做标记了?
我们来看稍微复杂点的场景。我们看到 textarea 依赖了 attrs,所以编译完对应的 PatchFlag 应该是
_createVNode("textarea", _mergeProps({
"id": "textarea"
}, attrs), null, 16);
单独把这段代码拿出来跑是没问题的,但是由于 textarea 的外层还套了一些组件,attrs 是单独定义的一个变量,并不是响应式的。我们先不管 attrs 这个变量,把这段代码当做是模板里面的。在模板编译的时候,A 的 children 在编译的时候其实做了一层缓存,每次重新渲染的时候,不需要再去创建 children 的 VNODE,同时对于 children 来说,形成了一个闭包。如果这段代码编译的时候,把 children 做了缓存,会打上一个静态的标记,那么 attrs 拿到永远是第一次渲染的值。
<A>
{{
default: () => (
// children
)
}}
</A>
所以当点击 +1s 的时候,并不会触发视图的更新。这个时候只能放弃组件 A 的优化,children 不做缓存。因此一旦在某个子节点传入了一个非响应式的变量,它的所有父节点的 children 就要放弃缓存,因此在每次 re-render 的时候都会重新创建,优化并不是很明显。然而上面这种写法在 JSX 中还挺常见的。
除了 PatchFlags 之外,Vue 里有一个叫 SlotFlags 概念,来处理 children 的不同情况。上面的情况,需要把 children 标记为 DYNAMIC,来放弃对 children 的缓存。因此如果你用 JSX 来写 Vue 的话,基本上是享受不到 Vue 3 对模板做的优化。
[1]Babel JSX Plugin: https://github.com/vuejs/jsx-next
[2]JSX: https://facebook.github.io/jsx/
[3]Flow: https://flow.org/
[4]vue-tsx-support: https://github.com/wonderful-panda/vue-tsx-support
[5]jsx.d.ts: https://github.com/vuejs/vue-next/blob/master/packages/runtime-dom/types/jsx.d.ts
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/A_p0aDLMg0yIuQCaLzP91g
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。