Angular 之父爲什麼懟 React ?

大家好,我卡頌。

前幾天,Angular之父 「Miško Hevery」「Dan」 在推上發生了一段有趣的對話,對話背景大概是:

  1. 傳統SSR(服務端渲染)場景下使用的技術叫Hydration「Miško」 曾向 「Dan」 演示了一個新技術概念 —— Resumable

  2. 「Dan」 認爲這項技術不可行

  1. 「Miško」Qwik框架中實現了Resumable

  2. 「Dan」 表示在React中我們之所以沒有考慮Resumable,並不是因爲框架不好接入,而是因爲Resumable並不是更優解

  1. 「Miško」 表示這是喫不到葡萄說葡萄酸

那麼,Resumable到底是什麼技術?他和React在推進的RSCReact Server Component)有什麼區別?「Miško」 爲什麼會作出上述言論?

讓我們通過本文了解一下。

Resumable(恢復)是什麼

Resumable的概念源於一次思路的轉變。

雖然主流前端框架都支持SSR,但不管是ReactVue還是Angular,他們都是CSR(客戶端渲染)優先。

在這些框架中,SSR是在CSR的基礎上附加的新功能。

正是由於傳統前端框架都是 「CSR 優先」 的產物,才導致一些常見SSR問題,比如:

這些問題拉低了SSR場景下的 FCP[1](First Contentful Paint)與 TTI[2] 指標(time to interactive)。

下圖展示了SSR場景下hydrate的流程,包括 4 個步驟,只有在整個流程完成後應用才能響應交互:

  1. 下載HTML

  2. 下載所有JS文件

  3. 解析、執行JS文件(主要是框架及其依賴,還有業務邏輯代碼)

  4. 綁定事件(即hydrate操作)

在某些應用場景(比如電商、博客)下,除了第一步,其他步驟可能不是必須的。

比如,對於一個電商商品詳情頁,除了展示商品所需的HTML外,其他都不是首屏渲染所必須的。

這就是Qwik框架中Resumable技術的設計理念 —— HTML優先,JS按需下載:

要實現Resumable,需要拋棄傳統框架以CSR爲基礎(用JS生成HTML爲主)的思路,轉而以SSR爲基礎(以服務端生成HTML爲主),再在此基礎上附加CSR功能。

爲什麼叫 Resumable?

Resumable的理念概括起來就是 「按需下載、執行 JS」

所有JS代碼的下載及運行會延遲到需要的時候再執行。在如下官方示例 1[3] 中,會渲染一個按鈕,「按鈕的點擊回調對應代碼」 不會在首屏渲染時下載:

export default component$(() => {
  return (
    <button
      onClick$={() => {
        // 這部分代碼不會在首屏渲染時下載
        console.log('click');
        const div = document.querySelector('#container')! as HTMLElement;
        div.style.background = 'yellow';
      }}
    >
      執行
    </button>
  );
});

只有在點擊按鈕時,對應代碼纔會被下載並執行:

這就使得首屏渲染時需要下載及執行的JS文件大大減少,提高了FCPTTI指標。

實際上,如果以Chrome lighthouse的評分作爲評判依據,其他框架確實都難以望Qwik的項背

這項技術之所以叫Resumable(恢復),是因爲它與傳統Hydration技術在首屏渲染時客戶端邏輯的區別。

傳統Hydration技術在首屏渲染時,客戶端(比如瀏覽器)會全量執行框架代碼與業務邏輯代碼,並在此過程中完成:

而以上過程在Resumable技術中是發生在服務端的。比如,對於上述按鈕的例子,點擊回調對應的下述代碼會在服務端生成HTML時完成序列化:

onClick$={() => {
  console.log('click');
  const div = document.querySelector('#container')! as HTMLElement;
  div.style.background = 'yellow';
}}

序列化後的數據會以HTML屬性的形式存在:

當點擊事件發生後,框架的前端部分會根據HTML屬性(示例中的on:click屬性)向後端請求具體的JS代碼(即點擊回調對應的代碼)並執行。

一句話總結就是 —— 在Resumable技術中,一切以SSR爲主,部分在SSR時未完成的操作(比如交互邏輯對應代碼)會在需要觸發時(比如交互發生時)再 「恢復」 執行,所以這一技術叫Resumable(恢復)。

與 RSC 的區別

同樣是SSR相關技術,React團隊主導的RSCReact Server Component)與Resumable有什麼區別呢?

在講解他們的區別前,我們要先了解一個背景知識:React「CSR 優先」 的框架,而且他已經出現很多年了(13 年問世)。

雖然這些年出現了很多優秀的框架技術(比如SignalAOT),但React一直堅持這套 「重客戶端運行時」 技術架構。

在發佈React Hooks後,React團隊逐漸將重心轉移向服務端。由於其技術架構偏向客戶端運行時,所以將React直接改造爲 「SSR 優先」 顯然不現實。

爲此,React團隊的策略是 —— 提供SSR能力,再讓其他 「SSR 優先」 框架接入(主要是Next.js)。

所以,ResumableRSC的主要區別其實體現在框架底層實現層面。

區別 1:序列化方式

最大的區別體現在 「序列化數據」 方式的不同。

Resumable技術下,SSR時會將大量數據序列化爲HTML屬性或註釋,比如:

服務端完成了大部分工作,客戶端需要做的僅僅是按需反序列化數據,並執行對應邏輯。

RSC中,服務端組件會被序列化爲一種自定義JSX協議,並被流式傳輸。之所以沒有被序列化爲HTML字符串(就像Resumable那樣),是因爲數據被反序列化後並不直接是HTML,而是JSXJSX經由React處理後纔會映射到HTML,這麼做能保持服務端組件的子孫客戶端組件不丟失狀態。

比如如下RSC,根據id props從數據庫取不同數據,再將數據傳遞給子組件(客戶端組件):

function ServerCpn({id}) {
  const data = db.get(id);
  return <ClicentCpn {...data} />;
}

id props變化後,ClicentCpn組件內的狀態並不會丟失。就是因爲服務端傳輸來的ServerCpn是一種自定義JSX協議,而不是HTML字符串。

區別 2:變化監測方式

通過區別 1 可以發現,RSC中序列化的數據描述的是組件級別的內容(JSX描述組件)。

Resumable中序列化的數據粒度更細(比如描述點擊事件的回調邏輯,或者某個狀態)。之所以會有這種區別,是因爲兩個框架採用不同的變化監測方式。

當狀態變化後,React需要遍歷完整的組件樹才能計算出 「狀態變化產生的影響」。所以序列化數據只需要描述組件級別的內容就行。

Qwik(實現Resumable技術的框架)使用Signal監聽狀態變化,這使得他能精確定位 「狀態變化所產生的影響」,即精確定位狀態變化需要反序列化哪些數據。

區別 3:後續的發展

由於React是重客戶端運行時的框架,所以雖然RSCSSR技術,他的後續發展還是會與重客戶端運行時的技術綁定(比如SuspenseSelective Hydration)。

Resumable是重服務端技術,所以後續發展應該會圍繞服務端展開,比如:

Miško 的想法

瞭解了這些技術細節,讓我們回到開篇,爲什麼 「Miško」 會懟React呢?

實際上,這並不是 「Miško」 第一次對React發表看法。之前 「Miško」 就曾表示:即使React Forget Compiler成功問世,他也沒法解決props下鑽場景下的性能問題,並以此論證Signal技術的優越性:

在這裏我們不比較技術優劣。只是說單純用腳投票,除了React外,確實有很多框架都使用了Signal相關技術,比如:

「Miško」 看來,React團隊之所以不採用更優秀的技術,是由於一旦採用新技術,就沒法完美的向後兼容,勢必造成社區生態的割裂。

作爲Angular的作者,「Miško」 對這種後果再清楚不過了。

但是,React團隊卻認爲 —— React之所以沒有采用這些技術,是因爲自身的技術路線更優秀。

這裏 「Dan」 舉出的例子是HooksRSC

本文已經做過RSCResumable的比較。在筆者看來,兩者是不同技術路線(CSR優先還是SSR優先)下的優秀代表。

但就Hooks而言,筆者認爲Hooks優秀在其理念,而不是實現。同樣基於Hooks理念實現的Vue Composition API在使用體驗上比React Hooks更佳,比如:

之所以同樣理念的不同實現使用體驗不同,完全是由於底層的技術實現區別造成的(這裏指 「底層變化監測方式」 )。

所以,從這個角度想,筆者並不贊同React團隊的說法。

我想,這也是爲什麼 「Miško」 會認爲React團隊喫不到葡萄說葡萄酸。

總結

大佬們的討論總是理性、互相尊重且剋制的。「Miško」 在後續也表示了自己對React的誤判。

Qwik v1.0發佈時,「Dan」 第一時間送上祝福。

有意思的是,對於 「Dan」 的祝福,「Miško」 回覆道:我們都站在巨人(指React)的肩膀上。

這是不是說,我還是比巨人要高呢?

參考資料

[1] FCP: https://web.dev/fcp/

[2] TTI: https://developer.chrome.com/docs/lighthouse/performance/interactive/

[3] 官方示例 1: https://qwik.builder.io/examples/introduction/runtime-less/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MSQcDUyOHZLs9xNn6Nob4g