前端性能的终极方案Qwik

前端性能的终极方案Qwik

Qwik 是一个前端框架,语法类似 React 使用 JSX 和 Hooks,不过 Qwik 是全栈SSR框架,而且 Qwik 采用了一系列策略优化页面的首屏性能,做的无论应用体积多大,首屏性能 PageSpeed 测试基本都能达到满分 Framework Benchmarks。

Misko Hvery,Qwik 的作者,更为知名的Title是Angular.js&Angular的作者。他在圣克拉拉大学毕业后先后去了硅谷很多公司,Intel、Xerox(施乐)、Sun和Adobe公司他都有工作过,在这些公司他主要从事数据库/后端方面的工作。

2005年加入了谷歌,2009年和Adam Abrons一起开发了Angular.js,后来成为谷歌的项目。

2021 年从谷歌离职加入builder.io成为CTO并开始了 Qwik 项目。

一个问题

为什么前端框架层出不穷,Svelte、SolidJS、Astro、Fresh、Marko、Qwik?

Big Runtime

React 和 Vue 都是基于 Runtime 的框架,即框架本身有很多的代码,且都会打包到最终的产物中被发送到用户的浏览器里执行,当用户需要在页面上执行操作改变组件的状态时,框架的 Runtime 会根据新的组件状态(State)计算(Diff)出需要更新的DOM节点从而更新View。

800_auto


从上图可以看出,像 Angular、React、Vue 等 Big Runtime 的框架体积都比较大,在首屏加载的时候就需要加载框架的JS文件并且执行相关的代码,那么就会产生一定的性能开销,尤其是弱网和低性能手机,性能影响就会越大。

Virtual DOM is Pure Overhead

Virtual DOM is pure overhead

Virtual DOM 并不高效。说Virtual DOM高效是因为它不会直接操作原生DOM,操作DOM比较消耗性能。应用状态变化时框架会生成新的 Virtual DOM,并通过diff算法去计算出本次数据更新真实的视图变化,然后只改变“需要改变”的DOM节点。

React 顶层组件 state更新,如果不进行任何优化,则所有子组件都会重渲染(re-render)。所谓的re-render是你 Class Component 的render方法被重新执行,或者函数组件被重新执行。组件被重渲染是因为Vitual DOM的高效是建立在 Diff 算法上的,而要有 Diff 一定要将组件重渲染才能知道组件的新状态和旧状态有没有发生改变,从而才能计算出哪些DOM需要被更新。

正是因为框架本身很难避免无用的渲染,React才允许使用一些诸如shouldComponentUpdate、PureComponent、memo和useMemo的API去告诉框架哪些组件不需要被重渲染。

在 React 16 版本之前,Virtual DOM 的对比是通过递归实现,如果组件树嵌套很深,那性能势必降低;React 16 之后,推出 Fiber 架构,虽然省不掉必要的 render,但把递归 Diff 改为可打断的循环,并且花费精力解决任务优先级调度问题,优化了用户体验。

Virtual DOM 还有个最大的问题——额外的内存占用,以 Vue 的 Virtual DOM 对象为例,100W 个空的 Virtual DOM(Vue) 会占用 110M 内存。

我认为最重要的问题是组件状态到页面元素是有映射关系的,而是用 Virtual DOM 则丢失了这个映射关系,需要 DOM Diff 来重新构建这个关系,纯粹是多余的消耗(Pure Overhead)。

SSR Hydration is Pure Overhead

Hydration is Pure Overhead

Hydration 是为了对服务端进行渲染的HTML提供交互能力的方案。Hydration 的过程一般是运行框架和组件代码,还原应用状态和构建Virtual DOM,并将事件监听添加到HTML元素上。


Misko Hevery认为Hydration是很低效的解决方案。

800_auto
从上图可以看出,Hydration 需要较长的时间来进行应用的状态恢复,主要因为以下两点:

1、框架必须下载与当前页面相关的所有组件代码并且由浏览器引擎进行解析和执行。

2、框架必须执行与页面上的组件关联的代码重新构建整个应用程序,以重建事件监听和内部组件树,即使实际上没有创建任何新的DOM。

所以 Hydration 是纯开销,因为整个应用的构建过程在 Node 上都已经运行过了,但是这部分信息没有同步到浏览器,而是丢弃掉了,所以客户端需要重新执行一遍代码进行 Hydration 来重新恢复整个应用。而如果服务端将所有的应用所需要的信息序列化传输到浏览器,那么 Hydration 的过程完全可以省略。

而且 Hydration 是与应用的复杂度成正比的。所以即使用了 SSR,页面的 TTI 也可能不是很好。

社区的探索

Precompile

如今很多新的框架都没有 VDOM,反而是通过预编译然后直接进行细粒度的 DOM操作来达到比 VDOM 更好的性能。

800_auto
Svelte 是 Precompile 的先行者,其通过静态编译减少框架运行时的代码量,一个 Svelte2 组件编译了以后,所有需要的运行时代码都包含在里面了,除了引入这个组件本身,你不需要再额外引入一个所谓的框架运行时!

<a>{{ msg }}</a>

会被编译成如下代码:

function renderMainFragment(root, component, target) {
var a = document . createElement('a');
var text = document . createTextNode(root . msg);
a . appendChild(text);target . appendChild(a)return {
update: function (changed, root) {
text . data = root . msg;},teardown: function (detach) {
if (...

点击查看剩余70%

{{collectdata}}

网友评论