新时代的 SSR 框架破局者:qwik( 五 )


对比传统的 hydration 方案,在客户端获得服务端下发的 HTML 后会立即请求需要的 JS 脚本并执行从而为页面附加对应的交互效果 。
而 qwik 提出的概念恰恰相反,获取完服务端下发的 HTML 页面后所有的交互效果实际上都是一种惰性创建的效果 。
因为我们在 HTML 中的每个元素中都已经通过序列化从而在它的标签属性上记录了对应事件处理函数的位置以及脚本内容(自然内容中也包含对应的状态),所以当获得 HTML 页面后其实就可以说此时页面已经加载完毕了而不需要任何实时的 JS 执行 。
这样做的好处是在 qwki 中完全可以省略 hydration 的多余步骤,甚至可以说完全抛弃了 hydration 的概念 。
客户端完全不必和服务端的 HTML 进行水合,相同的渲染内容仅仅是在 Server 端进行一次渲染客户端即可拥有对应的事件处理内容 。
简单来讲Qwik的工作原理就是在服务端序列化 HTML 模版,从而在客户端延迟创建事件处理程序,这也是它为什么非常快速的原因 。
3)qwik 工作机制
上边我们讲到了 qwik 的原理部分,同样拿上边的计数器的例子我们来对比下:
export const Main = () => <><Greeter /><Counter value=https://www.isolves.com/it/cxkf/kj/2023-02-28/{10}/>export const Greeter = () => {return ()}export const Counter = (props: { value: number }) => {const store = useStore({ count: props.number || 0 });return ()}在 qwik 编译后,服务端会序列化对应组件的 HTML 结构从而下发如下的模板:
<div q:host><div q:host><button on:click="./chunk-a.js#button">Trip Biz</button></div><div q:host><button q:obj="1" on:click="./chunk-b.js#count[0]">10</button></div></div><script id="qwikloader">/* qwik 中设置全局事件监听器的代码 */</script><script id="qwik/json">/* 用于反序列化的 JSON 相关信息 */</script>我们可以看到经过 qwik 编译后的 html 结构并不单单只有 DOM 元素,同时会在对应需要状态 & 事件的 DOM 元素上通过 HTML 元素属性来记录当前元素的事件和状态信息,这既是 qwik 中的序列化 。
比如上边 button 的 on:click 属性记录了该元素后续需要恢复的所有信息 。
需要注意的是序列化这一步是在服务端渲染时完成的,这也就意味着后续客户端可以通过服务端序列化的属性信息进行反序列化从而达到所谓的可恢复性而不需要重复执行组件 。
当然你可能会好奇 qwik 是如何进行这些事件 & 状态的恢复,qwik 正是通过在返回的 HTML 页面中内嵌的所谓 qwikloader 的 script 脚本(这段脚本的大小不超过 1kb)配合 qwikjson 映射表,从而在全局进行恢复事件和状态的逻辑 。
正因为这个原因,使得 qwik相较于传统 SSR 的 hydration 在 Client 中再次执行渲染从而水合页面状态和事件处理程序,这简直可以说是接近零 JS 的执行过程 。
最终在用户触发事件时候达到惰性的创建事件并执行,这个过程中完全没有重复任何服务器已经完成的任何工作 。
整个工作过程就像下面这张图描述的那样:
 

新时代的 SSR 框架破局者:qwik

文章插图
 
上边的这张图完美的描述了 qwik 的工作原理,相信经过上述的描述大家对于这张图中想表达的思想已经可以完美的理解了 。
 
利用 qwik 的这个优势,在绝大多数应用中我们可以利用 qwik 保证你的 SSR 应用在保证快速的 FCP 的前提也同样拥有与之不相上下的 TTI 体验效果 。
4)惰性加载脚本会影响用户交互体验吗
当然上文说过任何框架的优势和劣势都不是绝对的,在我们看来 qwik 的确会存在以下一些问题 。
大多数同学看完上边的内容我相信也会存在“惰性加载脚本会影响用户交互体验吗”这样的疑问 。
首先,qwik 中既然选择在触发用户行为时,再惰性加载并执行响应的 JS 脚本 。那么难免需要在用户触发交互时动态生成对应的事件处理函数进行执行 。
这样的方式相较于传统 hydration 的确会存在一些不足,需要额外生成事件会额外造成交互响应时间的损耗而传统 SSR 方式在页面首次加载时就已经绑定好(相当于生成了)相应的事件处理函数 。
就惰性加载生成事件这点:
针对于动态加载 JS 脚本,其实已经存在诸如非常多的 prefetch 等等预加载技术 。


推荐阅读