Angular 之父爲什麼懟 React ?
大家好,我卡頌。
前幾天,Angular
之父 「Miško Hevery」 和 「Dan」 在推上發生了一段有趣的對話,對話背景大概是:
-
傳統
SSR
(服務端渲染)場景下使用的技術叫Hydration
,「Miško」 曾向 「Dan」 演示了一個新技術概念 ——Resumable
-
「Dan」 認爲這項技術不可行
-
「Miško」 在
Qwik
框架中實現了Resumable
-
「Dan」 表示在
React
中我們之所以沒有考慮Resumable
,並不是因爲框架不好接入,而是因爲Resumable
並不是更優解
- 「Miško」 表示這是喫不到葡萄說葡萄酸
那麼,Resumable
到底是什麼技術?他和React
在推進的RSC
(React Server Component
)有什麼區別?「Miško」 爲什麼會作出上述言論?
讓我們通過本文了解一下。
Resumable(恢復)是什麼
Resumable
的概念源於一次思路的轉變。
雖然主流前端框架都支持SSR
,但不管是React
、Vue
還是Angular
,他們都是CSR
(客戶端渲染)優先。
在這些框架中,SSR
是在CSR
的基礎上附加的新功能。
正是由於傳統前端框架都是 「CSR 優先」 的產物,才導致一些常見SSR
問題,比如:
-
首屏渲染時,頁面短時間無法響應交互,因爲此時框架還未
hydrate
完成 -
即使僅有部分內容需要交互,但整個頁面還得全量
hydrate
這些問題拉低了SSR
場景下的 FCP[1](First Contentful Paint)與 TTI[2] 指標(time to interactive)。
下圖展示了SSR
場景下hydrate
的流程,包括 4 個步驟,只有在整個流程完成後應用才能響應交互:
-
下載
HTML
-
下載所有
JS
文件 -
解析、執行
JS
文件(主要是框架及其依賴,還有業務邏輯代碼) -
綁定事件(即
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
文件大大減少,提高了FCP
及TTI
指標。
實際上,如果以
Chrome lighthouse
的評分作爲評判依據,其他框架確實都難以望Qwik
的項背
這項技術之所以叫Resumable
(恢復),是因爲它與傳統Hydration
技術在首屏渲染時客戶端邏輯的區別。
傳統Hydration
技術在首屏渲染時,客戶端(比如瀏覽器)會全量執行框架代碼與業務邏輯代碼,並在此過程中完成:
-
框架組件對應的樹狀數據結構初始化(比如在
React
中叫Fiber
樹,在Vue
中叫VNode
樹) -
組件內狀態初始化
-
事件綁定
而以上過程在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
團隊主導的RSC
(React Server Component
)與Resumable
有什麼區別呢?
在講解他們的區別前,我們要先了解一個背景知識:React
是 「CSR 優先」 的框架,而且他已經出現很多年了(13 年問世)。
雖然這些年出現了很多優秀的框架技術(比如Signal
、AOT
),但React
一直堅持這套 「重客戶端運行時」 技術架構。
在發佈React Hooks
後,React
團隊逐漸將重心轉移向服務端。由於其技術架構偏向客戶端運行時,所以將React
直接改造爲 「SSR 優先」 顯然不現實。
爲此,React
團隊的策略是 —— 提供SSR
能力,再讓其他 「SSR 優先」 框架接入(主要是Next.js
)。
所以,Resumable
與RSC
的主要區別其實體現在框架底層實現層面。
區別 1:序列化方式
最大的區別體現在 「序列化數據」 方式的不同。
在Resumable
技術下,SSR
時會將大量數據序列化爲HTML
屬性或註釋,比如:
-
DOM
與Qwik
組件的關係 -
狀態(是的,狀態都會在服務端序列化爲
HTML
屬性,再在客戶端恢復) -
代碼邏輯(比如上述示例中的點擊回調邏輯)
服務端完成了大部分工作,客戶端需要做的僅僅是按需反序列化數據,並執行對應邏輯。
在RSC
中,服務端組件會被序列化爲一種自定義JSX
協議,並被流式傳輸。之所以沒有被序列化爲HTML
字符串(就像Resumable
那樣),是因爲數據被反序列化後並不直接是HTML
,而是JSX
,JSX
經由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
是重客戶端運行時的框架,所以雖然RSC
是SSR
技術,他的後續發展還是會與重客戶端運行時的技術綁定(比如Suspense
、Selective Hydration
)。
Resumable
是重服務端技術,所以後續發展應該會圍繞服務端展開,比如:
-
支持更多類型數據的序列化(當前不支持
class
序列化) -
支持序列化數據的流式傳輸
-
支持對 「是否序列化數據」 更精細的控制
Miško 的想法
瞭解了這些技術細節,讓我們回到開篇,爲什麼 「Miško」 會懟React
呢?
實際上,這並不是 「Miško」 第一次對React
發表看法。之前 「Miško」 就曾表示:即使React Forget Compiler
成功問世,他也沒法解決props下鑽
場景下的性能問題,並以此論證Signal
技術的優越性:
在這裏我們不比較技術優劣。只是說單純用腳投票,除了React
外,確實有很多框架都使用了Signal
相關技術,比如:
-
Vue
-
Preact
-
Qwik
-
新版
Angular
-
Solid.js
在 「Miško」 看來,React
團隊之所以不採用更優秀的技術,是由於一旦採用新技術,就沒法完美的向後兼容,勢必造成社區生態的割裂。
作爲Angular
的作者,「Miško」 對這種後果再清楚不過了。
但是,React
團隊卻認爲 —— React
之所以沒有采用這些技術,是因爲自身的技術路線更優秀。
這裏 「Dan」 舉出的例子是Hooks
和RSC
。
本文已經做過RSC
與Resumable
的比較。在筆者看來,兩者是不同技術路線(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