瀏覽器要原生實現 React 的併發更新了?
大家好,我卡頌。
要說React有什麼其他框架沒有的、獨一無二的特性,那一定是 「併發更新」。圍繞併發更新,存在兩個很有意思的現象:
-
很多開發者聽說過他
-
很少開發者直接使用過他
這兩個現象看似矛盾,其實很好解釋 —— React18 之後的新特性,主要是面向上層框架的(主要是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