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

語言: CN / TW / HK

引言

今天這篇文章中和大家聊一聊號稱世界上第一個 O(1) 的 JavaScript SSR 框架:qwik。

別擔心,如果你不是特別瞭解 SSR 也沒關係,文章大概會從以下幾個方面作為切入點:

  • 🌟 首先會圍繞對比 SSR 與 SPA 各自的優劣勢,從而展開 SSR 的運行機制以及 SSR 相較於 SPA 究竟為了解決什麼問題。

  • 🌟 之後,會根據 NextJs 的運行機制思考針對目前主流 SSR 框架設計思路上存在的不足從而引出 qwik 為何會在眾多成熟框架中脱穎而出。

  • 🌟 最後,筆者會針對於 qwik 提出自己的看法以及聊聊目前 qwik 存在的”問題“。

諸如社區內部 SSR 框架其實已經產生了非常優秀的作品,比如大名鼎鼎的 NextJS 以及新興勢力代表的 Remix 和 isLands 架構的 Astro、Fresh 等等優秀框架。

為何 qwik 可以在眾多老牌優秀框架中脱穎而出。接下來,讓我們一起來一探究竟吧。

SSR & CSR

目前業內存在非常多基於 SSR 的優秀框架,比如 Next、Remix、Nuxt 等等。

針對於 Qwik 我們先來聊聊基於 Next 體系的傳統 SSR 方案。

Client Side Rendering

在開始 SSR 之前我們先來聊聊它的對立面,所謂的 CSR(Client Side Rendering)。

服務器端渲染 (SSR) 是一種在服務器中進行渲染 HTML 而不是由瀏覽器中執行 JS 獲得網頁(SPA)的技術。

目前國內社區中主流框架比如 VueJs、React 等嚴格意義上來説都是基於 CSR(Client Side Rendering) 的產物。

所謂 CSR 的意味着當發出一個請求時,服務器會返回一個空的 HTML 頁面以及對應的 JavaScript 腳本。

比如

```html 攜程商旅

```

當瀏覽器下載完成對應的 JS 腳本後才會動態執行對應的 JS 腳本然後在返回的 HTML 頁面上進行渲染頁面內容。

你可以簡單的理解為上述的 ./index.js 會在客户端下載完成後執行該腳本,從而執行 document.getElementById('root').innerHTML = '...' 來進行頁面渲染。

這種方式並不是從服務端下發的 HTML 文件來進行渲染頁面,相反而是通過瀏覽器獲取到服務端下發 HTML 中的所有的 JS 文件後執行 JS 代碼從而在客户端通過腳本進行頁面渲染。

以及通常在 CSR 中當我們點擊任何頁面中的導航鏈接並不會向服務端發起請求,而是通過下載的 JS 腳本中的路由模塊(比如 ReactRouter、VueRouter 這樣的模塊)重新執行 JS 來處理頁面跳轉從而進行頁面重新渲染。

上面的概念是非常典型的 CSR ,瀏覽器僅僅接受一個用作網頁容器的 HTML 頁面,這樣的方式通常也被稱為單頁面應用 (SPA)

優勢

那麼上述我們提到的 CSR 廣泛存在於目前大量頁面中,必然存在它自己的優勢。

在頁面初始化訪問後加載速度極快且響應非常迅速。 在頁面初始化後,網站所有的 HTML 內容都是在客户端通過執行 JS 生成,並不需要再次請求服務器即可重新渲染 HTML 。

此外,有關任何實時的數據獲取都可以通過 AJAX 請求對於頁面進行局部更新從而刷新頁面。

劣勢

可是,CSR 真的有那麼完美嗎。任何一件技術方案一定存在它的兩面性,我們來看看 CSR 方式究竟存在哪些問題:

  1. 初始加載時間長。首次請求完服務器獲取到 HTML 頁面後,初始化的頁面仍然需要在一段時間內處於白屏狀態。

    在初始渲染之前,瀏覽器必須等待 HTML 頁面中的所有 Javascript 腳本加載完成並且執行完畢,此時頁面才會進行真正的渲染。

當然,使用代碼拆分或延遲加載等多種方案可以有效的減少上述的問題。但是這些方式始終是治標不治本,因為它並沒有從本質上解決 CSR 存在的問題。

  1. SEO(搜索引擎優化) 的負面影響。

    上邊我們提到過,所謂 CSR 本質上首先會返回一個空的 HTML 頁面,所以這也就造成了在搜索引擎對於該頁面的數據爬取中會認為它是一個空頁面。從而影響對應的搜索結果排名。

    雖然説在最新的 Google 中已經可以觸發執行 JS 對於網站進行關鍵字排名,但是在 JS 體積足夠大的時候針對於 SEO 仍然是存在一部分問題導致無法解析出正確的關鍵字匹配。

當然 CSR 還存在一些其他方面的缺點,比如網站強依賴於 JS 當用户禁用 JS 時網站只能是白屏展現給用户等等之類。

Server Side Render

簡單聊完客户端渲染後,我們稍微來看看所謂的服務端渲染是什麼含義。

基於舊時代的類似 Java 的 JSP 頁面我在這裏就不贅述了,顯然 JSP 的方式每個 HTML 都需要單獨請求服務器返回對應的 HTML 內容嚴格意義上來説這也是 SSR 的方式但是很明顯這已經被時代淘汰了。

目前國內各家公司廣泛應用的服務端渲染技術大概的思路是這樣的(Next 的 SSR 模式也是同樣的思路):

當用户首次訪問你的應用站點時:

  1. 首先服務器會根據對應的 URL 在服務端根據對應路徑渲染對應的 HTML 模版。

    注意這裏渲染的 HTML 模版是具有該頁面真正的內容。同時它並不具備任何交互邏輯(比如 DOM 元素的點擊事件),這是一份完全的靜態站點。

  2. 服務器會下發這份僅具有靜態內容的 HTML 模版,同時這份模版中也會包含對應的 JavaScript 執行腳本。

    第一時間會展示給用户對應的 HTML 頁面,此時對於訪問站點的用户來説**首屏渲染相較於 SPA 應用來説會非常快**。因為它並不需要在客户端瀏覽器上再次下載和執行 JavaScript 腳本來進行頁面渲染。
    
    其次,針對於 SEO 的優化也會非常良好,因為服務器上下發的 HTML 頁面是包含當前站點的真實 HTML 結構,對於搜索引擎的爬蟲來説會非常容易的匹配到當前關鍵字。
    
  3. 之後,瀏覽器會下載當前這份 HTML 的 JS 腳本。

    因為首先呈現給用户的一份靜態的 HTML 頁面,並不具備任何交互效果。我們需要為頁面上的元素增加對應交互,HTML 頁面中的 JS 腳本中會包含網站的交互邏輯。

  4. 最後,當下載完 HTML 腳本中的 JS 腳本後,自然會執行這些 script 腳本。從而發生一種被稱為 # hydrate(水合) 的方式,從而為頁面上靜態 HTML 元素再次添加對應的事件處理從而保證頁面具有交互性。

當 hydration 過程完成後,會由我們的客户端框架接管網站的後續渲染。 在後續的導航鏈接跳轉和頁面渲染中和服務器已經沒有任何關係了,我們完全可以利用客户端的路由切換(History Api/Hash Api)利用 JS 進行頁面渲染從而保證切換頁面不用再次請求瀏覽器保證非常及時的頁面交互。

hydration

上述過程中有一個非常重要的關鍵字 hydration(水合)。

首次訪問頁面時,頁面的靜態 HTML 是在服務端生成的。 在服務端我們將生成的靜態 HTML 以及 HTML 中攜帶的 JS 腳本發送到客户端。

此時靜態 HTML 會立即顯示在用户視野中,然後瀏覽器會利用網絡進程下載當前 HTML 腳本中的 JS 腳本。

當 JS 腳本下載完成後,會立即執行同時發生一種被稱為 hydration 的過程。

所謂的 hydration 簡單來説,也就是客户端下載完成 JS 腳本後,瀏覽器會執行下載的 JS 腳本這些腳本中有部分內容會將已經存在的 HTML 內容通過執行下載的 JS 腳本添加上對應的事件監聽器 從而保證頁面的交互。

注意,在 React、Vue 中 hydration 並不意味這重新渲染。因為在 Server 端已經渲染了和 Client 完全相同的 DOM 結構所以完全沒有必要在此重新渲染。

所以 hydration 的過程是給當前頁面中已經生成的 HTML 頁面添加上對應的事件監聽器。

這也是為什麼在 Next 等框架中為什麼必須要保證 Server 端和 Client 的渲染 HTML 結構必須一致的原因。

比如我們以 Next 舉例來説(Vue 也是同樣的道理):

  1. 當用户訪問 www.trip.biz.com 時,服務端接收到請求調用 ReactDOMServer.renderToString() 生成當前頁面的 HTML 靜態結構。

  2. 服務器會下發這個 HTML 頁面給客户端,同時這個 HTML 頁面上也會攜帶一部分 JS 腳本 script 標籤。

  3. 用户的瀏覽器中會立即展現到該 HTML 頁面,同時也會下載對應 JS 腳本並執行。

  4. 當 JS 腳本執行完畢後,客户端會調用 ReactDOM.hydrate() 發生水合為當前頁面的 HTML 頁面添加事件交互處理,同時後續由 JS 接管頁面的跳轉渲染。

針對於第一步 Next 中存在 Automatic Static Optimization 的優化,並不一定會在每次訪問時調用 renderToString 方法,有可能在構建時也會直接生成對應的 HTML 模版。

當然,在最新的 Next 版本中已經支持了Stream以及 Server Components

整個過程就像是這張圖中的樣子:

image.png

優勢

簡單聊過了所謂 SSR 的原理後,如果你有認真看上述的內容。其實我相信相較於 CSR ,SSR 這種方式的好處不言而喻:

  1. 更好的搜索引擎優化 SEO 方式,HTML 模板是從服務端直接下發這也就導致搜索引擎爬蟲中更多的關鍵字匹配。

  2. 更快的首屏渲染,因為相較於 SPA 它少了在 Client 中下載和執行 JS 腳本後渲染的過程。

  3. 頁面不需要 JS 也可以正常渲染,雖然沒有 JS 意味着頁面失去了可交互性。但對於禁用 JS 的用户來説,展示一些靜態內容總比 SPA 應用的白屏來的更加友好一些對吧。

劣勢

當然,任何技術方案在不同場景下也存在它自己的不足。

  1. 強依賴於服務。

    針對於 CSR 的方式它是一種純靜態資源。我們可以直接將它放在 CDN 上就可以良好的用户訪問到,而 SSR 的方式必須依賴於一個服務器進行服務端預渲染。(當然純 SSG 應用我們不在這個討論範圍之內)

    同時,有服務的地方就存在併發壓力。當你需要為你的應用考慮服務端渲染的方式時,一定不要忘記為你的服務器進行壓測。

  2. Time to Interactive 可交互時間 (TTI) 的增長,雖然説 SSR 的方式有效的縮短了首屏加載的方式,但是會增加所謂的TTI(可交互時間)。

    所謂的 TTI 指標測量頁面從開始加載到主要子資源完成渲染,並能夠快速、可靠地響應用户輸入所需的時間。

    因為 SSR 的方式在用户訪問時會下發當前頁面中靜態的 HTML 內容,也就是所謂的 First Contentful Paint 首次內容繪製 (FCP) 會非常快速,但是頁面需要用户交互效果缺又需要下載和執行完成 JS 腳本發生 hydatrion 後才具有交互性。

    這也就造成頁面的 TTI 相較於 CSR 方式會有所差勁,因為 CSR 在渲染完成後就會立即具有交互性(不需要其他任何多餘步驟)。

qwik

上述聊了那麼多前置內置,終於要和大家切入正題了。

所謂磨刀不費砍柴功,上邊和大家強調現階段 SSR 的方案以及對應的優劣勢就是為了引入下面的內容。

首先,這篇文章的目的是為了讓大家在當前眾多 SSR 框架中思考性能方面是否可以有所提升的,在服務器方面不會過多的深入。

我們可以稍微思考下上述服務器端渲染的過程:

image.png

第一步我們需要在服務端獲取對應頁面的 HTML 頁面,大多數情況(非純靜態頁面)就需要在服務端掉用對應渲染方法渲染出 HTML 頁面。

那麼,如果我們能在第一步渲染 HTML 頁面時,就添加對應的事件處理。後續的 3 步是不是完全可以省略下來了對吧。

其實社區內部之前已經有非常多的方案來提升所謂 SSR 框架的性能方案。

比如 Remix 的 HTTP stale-while-revalidate 緩存指令

比如 astro 等新興框架的 Islands 架構方案,關於 Islands 有興趣的朋友可以參考神三元的這篇 Islands 架構原理和實踐

針對於上面的概念,我們直接來看看 qwik 中提到的 Hydration is Pure Overhead (完全多餘的 Hydration)。

Hydration 造成的開銷

首先針對於 Hydration 的過程,我們提過到首先會在服務器上進行一次靜態 HTML 渲染,之後當 HTML 下發到客户端後又會再次進行 hydrate 的過程,在客户端進行重新執行腳本添加事件。

Hydration 過程的難點就在於我們需要知道需要什麼事件處理程序,以及將該事件處理程序附加在哪個對應的 DOM 節點上。

這個過程中,我們需要處理:

  • 每一個事件處理程序中的內容,絕大多數框架中的狀態都做為閉包函數保存在內容中。所以需要 hydration 的過程來重新獲取狀態。

  • 其次,在搞清楚了每個事件處理函數的內容後。我們也需要將對應的事件處理函數附加到對應的 DOM 節點上,同時還要確保該監聽器的正確事件類型。

更加複雜每個事件處理函數中的內容是一個閉包函數,這個函數內部需要處理兩種狀態,APP_STATE 以及 FRAMEWORK_STATE。

  • APP_STATE:應用程序的狀態。簡單來説應用程序的狀態就是 HTML 事件中的各個狀態事件,如果不存在這些事件狀態那麼所有的內容都是沒有任何交互效果的。

  • FRAMEWORK_STATE:框架內部狀態。通常我們會利用諸如 React 或者 Vue 等框架進行接替渲染。如果沒有 FRAMETER_STATE,框架內部就不知道應該更新哪些DOM節點,也不知道應該在什麼時候更新它們。

通俗來説 Hydration 就是在客户端重新執行 JS 去修復應用程序內部的 APP_STATE 以及 FRAMEWORK_STATE。

同樣還是這這張圖

image.png

在圖中的前三個階段可以被稱為 RECOVERY 階段,這三個階段主要是在重建你的應用程序。

當從 Server 端下發的 HTML 靜態頁面後,我們希望它是具有交互效果的 HTML 正常應用程序。

那麼此時 hydartion 的過程必須經歷下載 HTML 、下載所有相關 JS 腳本、解析並且執行下載的 JS 腳本。

RECOVERY 階段是和 hydartion 的頁面的複雜性成正比,在移動設備上很容易花費 10 秒。

由於RECOVERY是昂貴的部分,大多數應用程序的啟動性能都不是最佳的,尤其是在移動設備上。

前三個階段被稱為 RECOVERY 的階段其實是完全沒有必要的,因為在服務端我們已然渲染過對應的 HTML ,但是為了應用程序的可交互性以及服務端僅保留了靜態的 HTML 模版導致不得不在 Client 上繼續執行一次 Server 端的邏輯。

總而言之,hydration 其實是通過下載並重新執行 SSR/SSG 呈現的 HTML 中的所有 JS 腳本並執行來恢復組建中的事件處理程序。

同一個應用程序,會被髮送到客户端兩次,一次作為 HTML,另一次作為 JavaScript。

此外,框架必須立即執行 JavaScript 以恢復在服務器上被丟掉的 APP_STATEFRAMEWORK_STATE。所有這些工作只是為了檢索服務器已經擁有但丟棄的東西!!

比如這樣一個例子:

```tsx export const Main = () => <>

export const Greeter = () => { return ( ) }

export const Counter = (props: { value: number }) => { const store = useStore({ count: props.number || 0 }); return ( ) } ```

上邊的例子中我們編寫了一個 Counter 的計數器組件,在傳統 SSR 過程中該組件會被渲染成為:

html <button>Greet</button> <button>10</button>

可以看到上邊的兩個按鈕不擁有任何處理狀態的能力。

要使網頁具有交互性,必須要做的就是通過下載對應 HTML 頁面中的 script 腳本並執行代碼從而恢復按鈕上的交互邏輯和狀態。

為了具有交互性,客户端不得不執行代碼實例化組件後重新創建狀態。

當上述過程完成後,你的應用程序才會真正具有可交互性。無疑,同一個組件的渲染邏輯被執行了兩遍,這是一個非常宂餘且耗費性能的過程。

Resumability: 更加優雅的 hydartion 替代方案

所以為了消除額外的開銷,我們需要思考如何避免重複的 RECOVERY 階段。同時還要避免上面的第四步,第四步是執行腳本後給現有的 HTML 附加正確的事件處理程序。

qwik 中提出了一個全新的思路來規避 RECOVERY 帶來的外開銷:

  1. 將所有必需的信息序列化為 HTML 的一部分。

    qwik 將需要的狀態以及事件序列化保存在 Server 端下發的 HTML 模版中,需要序列化信息需要包括WHAT(事件處理函數內容), WHERE(哪些節點需要哪些類型的事件處理函數), APP_STATE(應用狀態), 和FRAMEWORK_STATE(框架狀態)。

  2. 依賴於事件冒泡來攔截所有事件的全局事件處理程序。

qwik 中事件處理程序是在全局處理的,這樣我們就不必在在特定的 DOM 元素上單獨註冊所有事件。

  1. qwki 內部存在一個可以延遲恢復事件處理程序的工廠函數。

    該工廠函數主要用於處理 WHAT 階段,也就是用來識別某個事件處理函數中應該存在什麼腳本邏輯。

image.png

我們可以看到所謂的 Resumable 對比 Hydration 明顯可以省略不需要後三個階段,直接獲取 HTML 後頁面其實就已經準備完畢,這無疑對於性能的提升是巨大的。

對比傳統的 hydration 方案,在客户端獲得服務端下發的 HTML 後會立即請求需要的 JS 腳本並執行從而為頁面附加對應的交互效果。

而 qwik 提出的概念恰恰相反,獲取完服務端下發的 HTML 頁面後所有的交互效果實際上都是一種惰性創建的效果。

因為我們在 HTML 中的每個元素中都已經通過序列化從而在它的標籤屬性上記錄了對應事件處理函數的位置以及腳本內容(自然內容中也包含對應的狀態),所以當獲得 HTML 頁面後其實就可以説此時頁面已經加載完畢了而不需要任何實時的 JS 執行。

這樣做的好處是在 qwki 中完全可以省略 hydration 的多餘步驟,甚至可以説完全拋棄了 hydration 的概念。

客户端完全不必和服務端的 HTML 進行水合,相同的渲染內容僅僅是在 Server 端進行一次渲染客户端即可擁有對應的事件處理內容。

簡單來講Qwik的工作原理就是在服務端序列化 HTML 模版,從而在客户端延遲創建事件處理程序,這也是它為什麼非常快速的原因。

qwik 工作機制

上邊我們講到了 qwik 的原理部分,同樣拿上邊的計數器的例子我們來對比下:

```tsx export const Main = () => <>

export const Greeter = () => { return ( ) }

export const Counter = (props: { value: number }) => { const store = useStore({ count: props.number || 0 }); return ( ) } ```

在 qwik 編譯後,服務端會序列化對應組件的 HTML 結構從而下發如下的模板:

```html

```

我們可以看到經過 qwik 編譯後的 html 結構並不單單隻有 DOM 元素,同時會在對應需要狀態 & 事件的 DOM 元素上通過 HTML 元素屬性來記錄當前元素的事件和狀態信息,這既是 qwik 中的序列化。

比如上邊 button 的 on:click 屬性記錄了該元素後續需要恢復的所有信息。

需要注意的是序列化這一步是在服務端渲染時完成的,這也就意味着後續客户端可以通過服務端序列化的屬性信息進行反序列化從而達到所謂的可恢復性而不需要重複執行組件。

當然你可能會好奇 qwik 是如何進行這些事件 & 狀態的恢復,qwik 正是通過在返回的 HTML 頁面中內嵌的所謂 qwikloader 的 script 腳本(這段腳本的大小不超過 1kb)配合 qwikjson 映射表,從而在全局進行恢復事件和狀態的邏輯。

正因為這個原因,使得 qwik相較於傳統 SSR 的 hydration 在 Client 中再次執行渲染從而水合頁面狀態和事件處理程序,這簡直可以説是接近零 JS 的執行過程。

最終在用户觸發事件時候達到惰性的創建事件並執行,這個過程中完全沒有重複任何服務器已經完成的任何工作。

整個工作過程就像下面這張圖描述的那樣:

image.png

上邊的這張圖完美的描述了 qwik 的工作原理,相信經過上述的描述大家對於這張圖中想表達的思想已經可以完美的理解了。

利用 qwik 的這個優勢,在絕大多數應用中我們可以利用 qwik 保證你的 SSR 應用在保證快速的 FCP 的前提也同樣擁有與之不相上下的 TTI 體驗效果。

惰性加載腳本會影響用户交互體驗嗎

當然上文説過任何框架的優勢和劣勢都不是絕對的,在筆者看來 qwik 的確會存在以下一些問題。

大多數同學看完上邊的內容我相信也會存在“惰性加載腳本會影響用户交互體驗嗎”這樣的疑問。

首先,qwik 中既然選擇在觸發用户行為時,再惰性加載並執行響應的 JS 腳本。那麼難免需要在用户觸發交互時動態生成對應的事件處理函數進行執行。

這樣的方式相較於傳統 hydration 的確會存在一些不足,需要額外生成事件會額外造成交互響應時間的損耗而傳統 SSR 方式在頁面首次加載時就已經綁定好(相當於生成了)相應的事件處理函數。

就惰性加載生成事件這點在我看來:

針對於動態加載 JS 腳本,其實已經存在諸如非常多的 prefetch 等等預加載技術。

無論是基於傳統 Next 方案還是基於 qwik 這種惰性可恢復的方案,利用 prefetch 等預加載技術優先在網絡空閒時加載響應重要的 JS 腳本都是非常有必要的,所以這點在我看來並不是特別重要的問題。

延遲加載會帶來 bundle 數量的上升嗎

qwik 推崇的延遲加載其實已經是一項非常成熟的構建技術了。無論是使用 webpack、rollup 又或是其他任何構建工具都存在延遲加載 & 代碼分割的技術。

傳統構建工具中關於代碼分割會帶來以下兩點的困難:

  1. 需要開發人員自行去處理更加細粒度的代碼分割,當然這並不是最主要的。因為目前我們可以利用 Magic Comments 配合 Dynaic Imports 來解決需要手動切入多個入口點的問題。

當然,這一系列事情比起魔法註釋。因為 qwik 本身提倡的就是所謂的延遲加載,所以在框架內部已經幫我們足夠智能的去處理這個過程。

但是需要注意的是這並不意味着開發者無法自主去控制這個過程。只是在使用框架的過程中,qwik 希望開發者更加專注於他們自身的業務邏輯。

  1. 當存在非常多的延遲加載時,傳統構建工具會從一個大 bundle 分割成為無數個小的 bundle 。

延遲加載模塊的確會存在多個 small bundle 的問題,可是當我們擁有的 bundle 越多,其實我們就擁有更多的自由度去以各種各樣的方式去拼裝成為單個大的 bundle。

qwik 中存在足夠的方式提供給我們將多個小的 chunk 自由組合成為一個從而有效的減少細碎 chunk 的數量,當然這個點在傳統構建工具中也是這樣。

動態創建事件函數會造成內存泄漏嗎

qwik 的設計思想在與每次事件觸發時通過 qwikloader 來動態創建事件處理函數,相信有的同學存在疑問“那麼多次觸發事件會造成額外的開銷嗎”。

qwik 的作者 miško heveryHydration is Pure Overhead 中明確的表示過 qwik 會在每次事件執行完畢後釋放函數,相當於每次事件執行完畢都會進行一次“去水合”的過程。

所以,當你觸發一次事件和無數次事件函數在執行過程中對於內存佔用來説是相差無幾的。

當然相較於傳統 hydration 的方式(在頁面首次渲染時在內存中記錄所有狀態),無疑 qwik 這種並不在內存中記錄任何狀態的方式恰恰對於內存的佔用比 dyration 更加輕量化。

qwik 真的有那麼快嗎

説了那麼多,那麼 qwik 真的有那麼快嗎。

WechatIMG144.png

上圖是利用 qwik 搭建的 builder.io 官方網站,我相信 builder.io 的數據已經告訴我們答案了。

固然上述的數據並不僅僅只是單純一個 qwik 框架帶給網站的優化,一定會有代碼層面或者構建層面等等方面的優化配合而來的數據。但是針對於 FCP 和 TTI 時間上的一致性這在一箇中型 SSR 應用程序其實可以稱得上是非常優秀了,我相信這足以説明了 qwik 的確名副其實。

結語

我們可以看出來,qwik 的核心思路還是通過更加細粒的代碼控制配合惰性加載事件處理程序以及事件委託來縮短首屏 TTI。

文章中我們也講到了 qwik 其實並不是因為使用了多麼牛逼的算法導致它有多麼快,而它的速度正是得益於它的設計思路,省略了傳統 SSR 下首屏需要加載龐大的 JS 進行 hydration 的過程。

當然,筆者對於 qwik 也仍是在學習階段。後續會在公司裏的更多項目嘗試 qwik 之後也會和大家分享關於它的更多心得。

總而言之,qwik 的”無水合“設計思路目前看來的確會在框架層面帶來巨大的性能提升。大家如果有機會的話也可以在項目中嘗試一下 qwik ,相信會給你帶來意想不到的收益效果。