尤雨溪主题演讲《2022 前端生态趋势》全记录
7 月 22 日上午观看了 第二届稀土开发者大会 尤大关于 《2022 Web 前端生态趋势》 的主题分享,记录如下,有时间的同学可以观看大会的回放:https://juejin.cn/live/xdc202201
先放一张整理好的大纲:
![](https://filescdn.proginn.com/2631b310f83e203cbdd32829d266609f/97af1d0d37db5f7f625109e9ee7f91c1.webp)
开发范式&底层框架
![](https://filescdn.proginn.com/a4c05c12439e7f843425dcfed44c931c/35208b12cd13404f8de073d0e168e648.webp)
React Hooks
在过去几年中,发生的最大的一个开发范式层面的一个变化肯定是 React Hooks,React Hooks 推出可以说是启发了很多组件逻辑表达和逻辑附用的新范式。
在 React 生态中,现在已经几乎彻底取代了 Class Components,在新的 React 项目中,大家已经很少看到采用 Class Components 的这些项目了。
在其他框架中,React Hooks 也产生了很大的影响。比如说 Vue 受到 Hooks 的影响推出了 Composition API,也就是组合式 API。而 Svelte 受到 Hooks 的影响推出了 Svelte 3。其实 Svelte 3 的整个的组件 编译的这个逻辑是由 React Hooks 启发而来的。
在 React 生态之外啊也有一些跟 React 更接近但是却不在 React 体系内的后起之秀。比如说像 SolidJS,它其实语法上跟 Hooks 语法更相似,但是实现上却跟 Vue Composition API 更相似的一个内在的实现。
所以说 React Hooks 还是启发了很多新的范式。
![](https://filescdn.proginn.com/e743717b5e93a539d5e74550fa212692/1beaef48df2521e6bceecdf53dd50817.webp)
React Hooks的开发体验问题逐渐被正视
虽然有这样的潮流的影响力在,但是随着 React Hooks 被大量的广泛使用,它的开发中的一些体验问题也逐渐的被大家所证实。这是一些不可回避的开发体验的问题。这里面根本的原因是在于 React Hooks 的执行原理和原生的 JS 心智模型上的一个差异,因为 React Hooks 是通过把组件的代码每一次更新都进行重复调用来模拟一些行为,那么这就导致了一些反直觉的一些限制:
![](https://filescdn.proginn.com/7961a54b38c7f4d026bc139342eada21/7cd56cb990cb8732e4d681de2fc57876.webp)
React 团队对改善开发体验的努力
很显然,作为一个竞争框架的作者,我会对 React Hooks 批评会更加直接一点,但是这些问题其实也并不是我一个人的看法,而是近年来 React 社区,甚至是 React 团队也已经意识到了一些问题。那么 React 团队在这些意识到这些问题后,其实也有在做改善开发体验的努力:
![](https://filescdn.proginn.com/66f5b35924e6a43d40b01239dc2dbba6/1deb7830df8a093ad6223168e8f4eaea.webp)
基于依赖追踪的范式重新得到重视
那么在这些探索或者说这些改进落地之前,很多 React 的社区成员以及包含一些本身就不使用 React 的用户,虽然React Hooks 产生了重大的范式影响,但是大家也意识到了它的一些问题。所以反而是跟 React Hooks 有相似的逻辑组合能力、但同时另一方面是基于依赖追踪的这些范式开始重新得到了重视。
![](https://filescdn.proginn.com/22f4c9e360406b4bedbbcb223863e475/5e8332b6510cf8f98ffccca22d5af199.webp)
举个例子来说,其实在 React 社区内部,也有像 Recoil 和 Jotai 这样的基于依赖追踪的一些状态方案。那么在社区之外就有更多了,比如说 SolidJS、Vue 的组合式 API。甚至老牌框架 Ember 最近也刚刚开源了一个新的项目叫做 Starbeam,是把 Ember 内部的状态的追踪管理的这个方案抽象出来。
Solid
![](https://filescdn.proginn.com/aad07c5a1834c3c8b70e84a5b8aa39e5/2c1a5f9042870e464a858de9a6a1e6f2.webp)
与 React Hooks 语法非常相似,副作用 createEffect
和 React Hooks 的 useEffect
类似,但是并不需要手动取声明依赖,其实在调用 count
这个函数的时候,它就默认自动帮你收集了依赖。
那状态更新的时候,我们并不需要去用 useCallback
,useEvent
这样的这额外的方式去创造一个一个函数来传给我们的事件监听器,这些都是非常符合直觉的。
Vue Composition API
![](https://filescdn.proginn.com/68b461143fa2c41e6fc7f3da88c4b54a/9377369fbb6407223117fc0c281771ed.webp)
那同样 Vue Composition API和 Solid 其实本质上内部实现几乎是一样的。只是 Solid 看上去更像 React,而 Vue 是更多的用这个 ref 对象,ref 对象上的这个 value 既可以用来读也可以用来写,在读和写之中就会自动的追踪和更新依赖。
Ember Starbeam
![](https://filescdn.proginn.com/434ec3f94dcb1d1cf4fe0484f6a62591/a63d29c10503be1673fbd8f24bb6aea4.webp)
而 Ember Starbeam,其实也可以看到它的这个 Cell
这个 API,其实就跟 Vue ref API 几乎是一样的。那么它的上面暴露了一个 current
来代表当前的这个值,以及暴露了一个 set
方法来行使状态更新。
基于依赖追踪的范式-共同点
![](https://filescdn.proginn.com/04168bcdc0198e60dbef0e92d852da43/8b0af943cba617c78ce2a50fcff895e3.webp)
不需要思考当重复调用时候会怎么样。不需要去思考把这个状态依赖哪些其他状态去一个一个列出来,这样对于整个的这个心智负担会小一些。无需使用 useCallback
,useEvent
这样的优化手段。
在 React 生态中的 Recoil 或者是 Jotai 这样的方案虽然也提供了自动的依赖追踪,和一定程度的组件的更新优化,但是因为它们仍然是需要在 React Hooks 的这个大的体系中使用的。所以呢在很多其他的方面依然会受制于 Hooks 的问题,那么 Hooks 本身在这些方案之外还是会存在这些过期闭包等等问题。
基于编译的响应式系统
![](https://filescdn.proginn.com/91f7495b2d2d4433141005c89d1921d1/5c351c2bc477153c152a3c1b2fb0fd8d.webp)
之前我们也说到 React Forget是一个基于编译时的优化去改善开发体验的一个手段。即使是基于依赖追踪的方案
也可以进行一些基于编译时的优化。
Svelte
![](https://filescdn.proginn.com/ef25d11f1492826143a9f143cc00a5b5/50947eb3cf8ec1713c8d6563088de836.webp)
可以看到这个 let
声明一个变量就是一个响应式的状态了,你要更新这个状态就直接去操作这个变量就可以。然后呢副作用是用一个神奇的编译式的魔法,也就是 $:
语法,使用 $:
label 声明副作用,当 count
发生变化,这个语句就会重新执行。使用编译的手段让编码更简洁。
Svelte 简洁的代价
![](https://filescdn.proginn.com/97319d9720f8b49685073d5182546426/a695a7bb01ead283e8fc041c88be47f8.webp)
Vue Reactivity Transform
![](https://filescdn.proginn.com/d3339310266438922fa32fb61a70d9a5/6ea16b533486df4eda2e5a594c1c6967.webp)
Vue 在 3.2 引入实验性功能 Vue Reactivity Transform,也就是响应式转换。可以看到还是一个简单的声明,使用一个 $ref
函数,这个函数其实是编译时的一个类似于宏的这样一个概念。这个函数并不是真实存在,只是给给编译器一个提示,那编译器通过编译之后,就会把它转化成我们之前看到的
这个基于真实的 ref 代码。
但是在使用时候体验就变成了只是声明一个变量,然后使用这个变量和更新这个变量就跟使用一个普通的 JavaScript 变量没有区别了。同时呢这个语法因为我们在声明的时候会显式声明说哪个变量是响应式的 哪个变量不是响应式的,所以这个语法其实可以在嵌套的函数中使用,也可以甚至在普通的 js、ts 文件中使用,它并不限制于 Vue 文件,所以这是一个比较更普适的基于编译的一个响应式模型。
Solid-labels
![](https://filescdn.proginn.com/58d6545b3d94b01d3c2694b248b79db9/fc798e4af5485feefc05d5ab69f1bcb5.webp)
那么在 Solid 生态中,其实也有受启发于 Vue Reactivity Transform,它的社区用户做的一个 Solid-labels,也是基于 Solid 的响应式方案。然后再做一层编译时的优化,那么可以看到跟 Vue Reactivity Transform 能够达成的效果非常相似。那最终的目的都是相似的,就是让大家可以用更简洁的代码去表达这个组件逻辑,同时又不放弃像 React Hooks 那样进行自由的逻辑组合的这些能力。所以说这也是一个很有意思的探索方向。
统一模型的优势和代价
![](https://filescdn.proginn.com/de8a8fa93292928499cacb297e243a67/c3f25cc2387c6d92f87f2b3198c480a9.webp)
和 Svelte 相比,Vue Reactivity Transform 和 Solid-labels 都是我所谓的统一模型。那也就是说它不受限于组件上下文,它可以在组件内使用也可以在组件外使用。它的优势就是有利于长期的重构和复用。为什么呢,因为很多时候我们的大型项目中的逻辑复用都是在我们一个组件,写着写着发现这个组件变得很臃肿,我们才开始考虑要把逻辑开始重新组织、抽取复用。那么 Svelte 由于他的语法只能在组件内使用,这就使得把逻辑挪到组件外成为一个
代价相当大的行为。并不是一个简单的说把这部分逻辑复制出去,而是需要进行一次彻底的重构。因为组件外用的是完全一套不同的系统。但是像用,Vue Reactivity Transform 和 Solid-labels 这样的方案,我们就可以把组件内的这些逻辑原封不动的直接拷贝到组件外,然后把它包在一个函数里面抽取就完成了,这样重构的代价非常小,也就更鼓励团队去进行更多的这样的重构和优化,对于长期的可维护性会有更好的帮助。
当然他的代价就是说因为需要显式的去声明响应式的这个变量,所以会有一定程度的底层实现的抽象泄露。也就是说用户其实是需要先了解底层的响应式模型的实现,然后才能更好的理解这个语法糖是如何运作的。而不像 Svelte 组件中的这个语法呢,即使你完全不了解它底层如何运作,你也可以几乎可以 0 成本的上手。那么这就是一个长期的可维护性和一个初期的上手成本之间的一个平衡和和取舍。
基于编译的运行时优化
![](https://filescdn.proginn.com/6b08c1a1e1861d4d9e2b04eac31cfbd7/43d829e5789f280b6cf687c81e975ce1.webp)
不同策略对代码生成量的影响
![](https://filescdn.proginn.com/164c23f8f2f3dbf244467b51b38606e2/7b4c3c54f68c4d9fcf31193a07417467.webp)
Svelte 代码生成策略相对更繁琐一些。而 Solid 是基于先生成一个基本的 HTML 字符串,然后在里面找到对应的 DOM 节点进行绑定。Svelte 是通过生成命令式的一个一个节点,然后把节点拼接这些 Javascript 代码。那这个策略就导致同等的这个组件源码之下 Svelte 每个组件的编译输出会更臃肿。虽然大家会第一印象是觉得说 Svelte 是以轻量而出名的,但其实我们会发现,在相对大型的项目中,在项目中组件超过 15 个之后,Svelte 的整体的打包体积优势就已经几乎不存在了。那么当组件超过 50 个,甚至是达到 100 个的时候,Svelte 的体积会越来越臃肿。而相对于而言,我们可以看到 Vue 和 Solid 的编译,整体的这个曲线就平缓很多。所以其实在越大型的项目中反而是 Svelte 的体积优势反而是一个劣势。据我所知 Svelte 团队也有在想要优化这一方面的想法,那么可能会在下一个大版本中才能实现,我们也会拭目以待。
第三方性能测评(7月):
![](https://filescdn.proginn.com/e1ab1607259e185c0d825925fadcd8ed/2e7a72abc228cad80e84b8478768d7c7.webp)
Vue 在追赶 Solid,Vue Vapor Mode 实验项目:
![](https://filescdn.proginn.com/06be9cdfac78cf7333e9c6381e6b7eed/fcee7b6d6dbc1d9eea78240db2c4d6f3.webp)
![](https://filescdn.proginn.com/e0ab1f6b3feedb1129e288d63ded4abb/e546771e1700c88a30f84fe597fde94c.webp)
整体的思路就是一次性事先生成这个模板的静态结构,一次性生成这些静态节点,然后再去生成命令式的找到动态节点并对把它跟状态进行响应式的绑定的这样一些代码。这个策略也是就是几乎本质上就是 Solid 所采用的策略,那么其实这个策略可以被所有的模板引擎所使用。所以我们也在探索在之后某个版本的 Vue 当中会引入一个可选的一个模式,把模板编译成这样的性能更优的,运行时体积也更小的一个模式。当然这不会是一个破坏性更新,因为我们的目标是可以让你渐进式的去使用这个功能。
工具链
![](https://filescdn.proginn.com/9b4471c05ea0434468ead3e66065f2f3/246d526fbeb34e4e976f1d8f8a0da50a.webp)
原生语言在前端工具链中的使用
![](https://filescdn.proginn.com/b34b9f2b5b5c3dedea8ca09ef0966b4b/506556bd82065ea59d9ccc4cdd8f2e65.webp)
![](https://filescdn.proginn.com/8effb1906f03250775b9a14abb9ab933/dc3fc6d3915783859bce26f618f638d6.webp)
工具链的抽象层次
![](https://filescdn.proginn.com/65f59f708e0df0ef2d5c3bfe81835a76/73e2960befb9f31e7366326ae8891b22.webp)
基于 Vite 的上层框架
![](https://filescdn.proginn.com/8d73e3bcdbd7d90781786df426bac180/793fefc4ec209c7f20fdab25a14c6968.webp)
上层框架 Meta Frameworks
![](https://filescdn.proginn.com/cfa2f961de918b33a481afaedfc4f3a0/0244944e32c1b4ff04857dfc68b38a31.webp)
JS 全栈的意义是什么
一个语言,前后“打通”
数据的前后端打通
![](https://filescdn.proginn.com/0dc38a2dfe5e0576f0e86d36812df20c/8678f9faaff8a75f7b1b900019c331af.webp)
类型的前后端打通
![](https://filescdn.proginn.com/642d371ad9c4573be7155d38b7406621/ac2a0fdb3bec1b95e80fdbc4344354c8.webp)
JS 全栈的代价
![](https://filescdn.proginn.com/878e8207899c0cd527193efb6059337e/3b3bb321794186d1ef1579b5793e64e4.webp)
社区的探索方向
![](https://filescdn.proginn.com/33e7d47705c64774c662d12d4c16305d/fa99d27caa07f221c9c18b7241e2c8d1.webp)
关于作者和声明
![](https://filescdn.proginn.com/c8e6843c37a0c553cef87da4ff762245/c57730148afab768c262ee0916e74f9a.webp)
![](https://filescdn.proginn.com/aa9a98fa2ce5d879a8058f60e3bb5cb4/ba6bf8355322aac1f343966d7db50149.webp)
文章转载于公众号:Frontend Radio