React 併發渲染的前世今生
大家好,我是 ConardLi。
2161 天!
這是 React
團隊從計劃爲 React
增加 併發渲染
的能力,到 React 18
可用版本發佈所花費的時間。
爲啥中間花費了這麼長的時間?中間又發生了哪些有趣的故事?我們回到 2016
年,來回顧一下 React 併發渲染
誕生的過程!
在 React 運行時優化方案的演進 一文中,我們從技術細節和實現原理的角度詳細解讀了 React
併發渲染的演進。但是技術細節太多,很多小夥伴表示讀起來比較困難,今天這篇文章會以更輕鬆的方式帶大家看整體的演進之路,不會涉及太多的技術性,讀起來會更簡單,相信看完這篇文章再去看之前的問會有不一樣的理解。
瀏覽器的瓶頸
早在 2016
年,React
就已經開始在前端屆爆火了。React
團隊始終有一個目標,就是給基於 React
而構建的上百萬個網站提供最好的性能體驗!
但是提升性能最大的瓶頸,不一定和 React
本身有關。而是與 React
建立在的語言 JavaScript
,以及 JavaScript
所在的瀏覽器環境有關。
瀏覽器會在一個主線程裏處理所有的 JavaScript
代碼、用戶事件、渲染、佈局、繪製以及重排。
通常情況下,它們互不打擾,相互運行的挺和諧的,但是如果一不小心,就有可能導致問題。
React Conf 2018
React
現在是同步的,這意味着當你更新組件時,React
會同步處理這個更新, 它會在一個主線程上持續工作,直到所有更新完成。所以問題在於,用戶事件也會在主線程上觸發,如果此時React
正在渲染更新,同時用戶嘗試以同步的方式輸入一些內容,React
會等待正在執行的所有渲染完成後才能去處理用戶事件。— React Conf 2018
Fiber 誕生
多線程渲染
所以,如果問題在於渲染阻塞了主線程,那我們不能在另外一個線程裏去完成渲染工作嗎?比如使用 webworker
?
但實際上這並不是 React
想要的, React
想要的是一種讓當前的渲染工作變得更靈活的方案。
React Cong 2017
我們有一些
IO
的工作,然後是一些CPU
的工作,在理想狀況下,我們應該能夠並行執行其中一些工作了。這不是一個性能問題,這基本上是一個調度問題了 — React Cong 2017
React
團隊發現,他們可以通過某種方式來優化 React
,以便可以區分低優先級和高優先級的工作。
例如,用戶輸入和動畫渲染屬於高優先級任務,他們可以讓 React
擁有在這些任務之前互相切換的能力。
理論上,通過這種方式,每個 React 應用的體驗都可以得到提升,因爲 React
總是最優先考慮最重要的工作。
這就是 React
團隊這段時間做的事情,他們將其命名爲 React Fiber
。
Fiber
並沒有被作爲一個新的框架,而是作爲一個主要的 React
版本:React 16
推出來了。
它讓 React
具有了異步可中斷的能力。
異步渲染
2017
年初,React
現在看起來更聰明一點了,它能夠優先處理一些工作,並且能中斷當前渲染。
但是,這個能力只能說是個半成品,另外還有一個非常困難的事情是找到一個公共 API
,讓 React
開發者以一種不會完全破壞當前 React
生態的方式使用這些能力。
解決這個問題的第一部分,是擺脫掉可能會對新的異步可中斷渲染的能力起到副作用的部分。
在新的架構中,一個組件的渲染被分爲兩個階段:第一個階段(也叫做 render
階段)是可以被 React
打斷的,一旦被打斷,這階段所做的所有事情都被廢棄,當 React
處理完緊急的事情回來,依然會重新渲染這個組件,這時候第一階段的工作會重做一遍。兩個階段的分界點,就是 render
函數。render
函數之前的所有生命週期函數(包括 render
) 都屬於第一階段。
React 16.3
如果我們在這些生命中期中引入了副作用,被重複執行,就可能會給我們的程序帶來不可預知的問題,所以到了 React v16.3
,React
乾脆引入了一個新的生命週期函數 getDerivedStateFromProps
,這個生命週期是一個 靜態方法,在裏面根本不能通過 this
訪問到當前組件,輸入只能通過參數,對組件渲染的影響只能通過返回值。
同時,React 團隊開始將這種新的模式稱爲 — async rending
。
React Conf 2018
這裏最大的問題不是性能,而是調度,所以我們必須考慮調度,所以我們稱這些新的能力爲
async rending
。我們的目標是可以讓程序開發者適應設備和網速等用戶限制,讓交互體驗變得更好。— React Conf 2018
Hooks
然而,一年後,dan
繼續表示:React
缺少了一些讓調度工作更簡單的東西,這就是 Hooks
。
Hooks
於 2018
年十月在 React comp
中發佈,它是 React
自發布以來最大的變化。
Hooks
最初的重點在於它可以讓你用函數式寫法替代類來創建 React
組件。
但實際上它們帶來的收益要更多,你可以更好的進行代碼複用、組合、設置默認值,另外還有比較重要的一點,Hooks
可以更自然的編寫出和異步渲染更兼容的代碼。
concurrent React
然後在這個階段我們還解鎖了一個新名字:concurrent React
。
React Conf 2018
async 是一個非常廣泛的術語,它可以描述很多內容,我們認爲
concurrent React
這個詞更恰當一點。concurrent React
可以同時處理多個任務,並且根據這些任務的優先級在它們之間切換;它可以讓渲染樹進行部分渲染,而不將最終結果提交給DOM
; 並且,最重要的,concurrent React
不會阻塞主線程。— React Conf 2018
concurrent mode
然而,這種說法並沒有持續多久,很快它就會 concurrent mode
替代了。
事件來到了 2019 年,我們終於得到了一些可以拿出來用的東西,concurrent React
正式更名爲 concurrent mode
。
React Conf 2019
concurrent mode
讓React
應用程序可以中斷較大的低優先級任務,以專注於更高優先級的事情(例如響應用戶輸入事件)。— React Conf 2019
concurrent mode
現在已經可以在實驗模式下使用了 — React Conf 2019
不容易,搞了三年了,用戶終於有一些可以使用的東西了。。。
但是,它是最終版本的 API
嗎?不是!它已經可以在生產環境使用了嗎?不能!
但是,concurrent mode
讓我們終於可以在程序裏面去體驗一下了,我們可以在實驗模式下開啓,這樣我們就可以看到併發渲染的性能優勢了。
但是,實際上,想法很美好,我們仍然受到了升級策略的限制。
升級策略
React
在以前是不可以多版本共存的,這意味着我們只能在一些 DEMO
項目和新項目中看到這種提升,如果我們想在已經存在的大型應用程序裏面去用,就需要一個更好的升級策略。
React 17
就是用來解決這個問題的,它在一年後的 2020 年 8 月發佈。
React 17
允許我們在同一個應用程序裏允許多個版本的 React
,這讓我們可以在大型項目裏採用增量升級策略,你可以將程序的部分升級到 React 18
。
然而,它實際起到的作用也沒有那麼好,因爲漸進式的升級策略也無法做到更精細的控制。
React
團隊還另外提供了一種稱之爲 blocking mode
的模式,是處於舊的模式和新的併發渲染模式之間的混合模式。
怎麼說呢?也是個弱雞的策略,沒有達到預想的效果,React
團隊在後續的一段時間收到了大量的用戶反饋。
concurrent features
這時,距離 React
宣佈新的架構開始,已經過去了 5
年的時間,在收到了大量的反饋後,React
團隊又做出了改變,這次,似乎來到這最終的解決方案?
React Conf 2021
在聆聽了大量的用戶反饋後,我們很高興的分享 —
concurrent mode
在React 18
中消失掉了,它被逐步採用的漸進式策略取代,你可以按照自己的節奏採用併發渲染。— React Conf 2021
concurrent features
— 這個名字很明顯,因爲無法做到直接全面升級併發渲染,React
希望提供給我們一些特性讓我們去選擇性的啓用併發渲染。
在這種模式下,你可以讓程序特定的部分啓用併發渲染。
useDeferredValue
我們需要通過一些 api,讓我們在整個渲染過程中確定工作的優先級,擁有可中斷的能力, 首先我們來看看 useDeferredValue
,它可以讓我們去標記某個具體狀態的優先級。
比如我們現在有這樣的場景,用戶輸入了一些搜索關鍵字後,我們需要將搜索到的數據渲染到下面的詳情裏,如果這個處理比較耗時,那麼連續的用戶輸入會有卡頓的感覺。實際上,我們希望的是用戶的輸入能得到快速的響應,但是下面詳情的渲染多等待一會其實無所謂。
這時,我們可以通過 useDeferredValue
創建一個 deferredText
,真正的意思是 deferredText
的渲染被標記爲了低優先級,用戶輸入已經不會有卡頓的感覺了。
startTransition
useDeferredValue
是讓我們標記哪些具體的狀態擁有更低的優先級,而 startTransition
可以明確的告訴 React
哪些更新具有更低的優先級。
當有一些更新被包裹在 startTransition
下時,React
將已較低的優先級去處理這些更新,從而優先去處理像用戶輸入這樣更高優先級的更新。
Suspense
另外你可能還會經常聽到的一個詞是 Suspense
,它的目標是讓我們在 React
組件中讀取遠程數據像使用 props
和 state
這樣簡單。
<Suspense/>
是一個 React
組件,如果組件樹有一些位置還沒準備好,它可以讓你以聲明的方式控制這部分渲染的 UI
。
它可以讓我們將左側這樣代碼簡化成右側這樣,讓你可以在 React
組件中以同步代碼的寫法編寫異步代碼。
React 18 是最終版本嗎
React 官方在官網中提到,大多數情況下我們都不會和這些併發渲染的 API
直接交互,這讓我們很難判斷 React 18
究竟是不是一個革命性的版本。
不管怎麼說,它是一個歷時兩千多天的、我們期待已久的巨大里程碑。
你認爲它是 React
併發渲染的最終版本嗎?
最後
參考:
-
https://www.youtube.com/watch?v=NZoRlVi3MjQ
code 祕密花園 這裏有最前沿的前端技術、最新的前端消息、最精品的技術文章、最好用的工具推薦、還有一個有趣的作者。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/01sTK6w4BFUzoRc2NKCs1w