瀏覽器要原生實現 React 的併發更新了?
大家好,我卡頌。
要說React
有什麼其他框架沒有的、獨一無二的特性,那一定是 「併發更新」。圍繞併發更新,存在兩個很有意思的現象:
-
很多開發者聽說過他
-
很少開發者直接使用過他
這兩個現象看似矛盾,其實很好解釋 —— React
18 之後的新特性,主要是面向上層框架的(主要是Next.js
)。
換句話說,這些新特性(比如併發更新)主要是供框架集成,而不是開發者直接使用。
比如,併發更新的兩個核心API
—— useTransition
和useDeferredValue
,都是針對 「視圖切換」 的場景。
而在前端交互中,最主要的 「視圖切換」 場景就是 「路由切換」,所以包含路由功能的前端框架就會集成這兩個API
。
而現在,一個試驗性瀏覽器API
—— View Transitions API
將原生實現 「視圖切換」 功能。
他到底有什麼用?如果其他框架使用它,是不是能獲得React
同樣的併發更新能力?
什麼是視圖切換?
不管是View Transitions API
,還是React
的useTransition
,都是爲了提高 「視圖切換」 場景下的用戶體驗。
什麼是 「視圖切換」 ?以 view-transitions Demo[1] 舉例。這是個簡單的相冊Demo
,點擊左邊圖片縮略圖,右邊會顯示大圖:
整個過程簡單來說包括 3 個步驟:
-
點擊縮略圖
-
請求大圖數據
-
大圖請求成功後,顯示大圖
從步驟 1 到 3 的過程就是個典型的 「視圖切換」。整個過程有很多可以優化體驗的地方,比如:
-
從舊圖到新圖的漸變過渡效果
-
點擊縮略圖發起圖片請求後,大圖區域可以先顯示舊圖(而不是立刻顯示
loading
效果),待新圖請求成功後再過渡到新圖
這裏解釋下第二點,對於切換類的交互,相比於 「當視圖切換時立刻顯示 loading 效果,待新視圖加載完成後過渡到新視圖」,「當視圖切換時先顯示舊視圖,待新視圖加載完成後過渡到視圖」 在延遲不高的情況下體驗會更好。
除了上述這些 「體驗優化的點」,視圖切換的實現還有很多細節需要考慮,比如:
-
如何處理新舊視圖切換時的過渡效果?
-
如何處理新視圖加載時的
loading
效果? -
當正在請求新視圖數據時(此時視圖處在舊視圖中),用戶又對舊視圖產生交互怎麼辦?
-
視圖切換時如何處理頁面滾動位置、光標聚焦(
focus
)位置? -
對於使用屏幕閱讀器的盲人,視圖切換時閱讀器會朗讀什麼?
在SPA
(單頁應用)出現之前,網站通常是由多個頁面組成。比如下面網站的每個Tab
欄,對應一個獨立網頁,其中:
在Tab
之間切換,瀏覽器會:
-
卸載之前的頁面
-
請求新頁面數據
-
加載新頁面
從 「頁面卸載」 到 「頁面加載」 之間的白屏間隙會造成屏幕閃爍。
要優化這種場景下優化視圖切換效果,當前唯一做法是利用history API
接管路由操作,將網頁變成一個SPA
。
既然 「視圖切換」 是如此常見的需求,且有這麼多需要考慮的因素,那瀏覽器爲什麼不原生實現呢?
於是,View Transitions API
應運而生。
當前
View Transitions API
不支持跨頁面的視圖切換,但未來會支持
View Transitions 的使用
View Transitions API[2] 的使用很簡單,只需要用document.startViewTransition
包裹視圖切換後的回調函數即可。
對於上述相冊示例,回調函數的邏輯是 「將 img 標籤 src 屬性更新爲新圖片地址」:
const transition = document.startViewTransition(() => {
galleryImg.src = /* 新圖片地址 */;
});
剩下所有跟過渡相關的實現,都由Transitions API
解決。
View Transitions 實現原理
在視圖切換時,存在 2 個概念:
-
切換前的舊視圖
-
切換後的新視圖
當使用View Transitions
後,會依次做:
-
對頁面進行截圖,作爲舊視圖
-
執行傳遞給
document.startViewTransition
的回調 -
DOM
更新後,對更新後的頁面進行截圖,作爲新視圖 -
構造一棵代表過渡效果的僞元素樹,掛載在根元素(
html
元素)下,結構類似如下:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
其中:
-
舊視圖保存在
::view-transition-old(root)
中 -
新視圖保存在
::view-transition-new(root)
中
對於上述相冊示例,掛載的僞元素樹結構如下:
之所以要掛載一棵僞元素樹,主要是因爲兩個原因:
- 開發者可以對微元素應用
CSS
規則
比如,上述兩個 「保存了新 / 舊視圖的截圖」 的僞元素,類似於img
標籤,開發者可以對他們應用CSS動畫
,當新 / 舊視圖切換時,實現自定義的過渡效果。
- 方便對整個頁面中不同 「視圖切換」 分組
比如,在上述相冊示例中,視圖切換的元素包括兩部分:
-
新 / 舊視圖之間的切換(下圖紅框部分)
-
新 / 舊圖片名稱的切換(下圖綠框部分)
相冊對應的HTML
結構如下:
-
img
標籤對應視圖部分(下圖紅框部分) -
figcaption
標籤對應圖片名稱部分(下圖綠框部分)
當我們爲figcaption
元素設置不同的view-transition-name
:
figcaption {
view-transition-name: figure-caption;
}
會得到一棵新的僞元素樹,其中 「視圖部分」 和 「圖片名稱部分」 僞元素是分離開的:
通過給頁面中不同HTML
元素定義不同的view-transition-name
屬性,就能獨立控制這個元素是視圖切換時的過渡效果。
與 React 的區別
瀏覽器原生的View Transitions API
與React
中的useTransition
相比,誰更強大呢?
毫無疑問,前者更強大。
這是因爲,對於View Transitions API
,通過操作僞元素樹,開發者可以自定義過渡效果(就像對img
元素使用CSS過渡動畫
一樣簡單)。即使不使用CSS Transition
,使用JS Transition
也完全沒問題。
document.startViewTransition
方法會返回一個transition
對象實例:
const transition = document.startViewTransition(() => {
galleryImg.src = /* 新圖片地址 */;
});
該實例包含了一系列視圖切換生命週期對應的promise
,比如:
-
ViewTransition.ready
:僞元素樹構造完成,準備開始過渡時 -
ViewTransition.finished
:過渡效果完成後,此時新視圖已經可以響應用戶交互
而在React
中,使用useTransition
後,與其說完成的是 「視圖切換」,不如說完成的是:
-
首先,完成狀態的切換
-
React
內部再將狀態變化映射到視圖變化
本質來說,操作視圖的是React
,而不是開發者。所以,開發者很難控制過渡效果。
動效庫Framer
的作者認爲,由於開發者很難控制併發更新的完整生命週期,所以很難在併發更新時表達animation
效果:
簡單來說就是,開發者很難爲併發更新定製過渡效果(用CSS
或JS
)。
總結
可以認爲,View Transitions API
是比useTransition
抽象程度更高、開發者可控性更高的API
。useTransition
能實現的,他可以。useTransition
不能實現的,他也可以。
要說缺點,View Transitions API
是Web
平臺獨有的,而useTransition
依賴React
核心的併發更新能力,是跨端的。
當前,View Transitions API
的兼容性並不好:
但是,一旦他變成可以大規模使用的API
,那麼其他前端框架只要接入他,就能輕鬆獲得比React
耗費大量精力實現的useTransition
(以及底層的併發更新特性)更強大的視圖切換能力。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/dcPiKDGhMgX6VJ9IK413Ww