React Fiber是什麼
使用React的同學們都應該要知道React Fiber,因為這玩意快要正式發布了。
React Fiber這個大改變Facebook已經折騰兩年多了,剛剛結束不久的React Conf 2017會議上,Facebook終於確認,React Fiber會搭上React下一個大版本v16的順風車發布。
在 Is Fiber Ready Yet? 這個網站上可以看到當前React Fiber的單元測試通過率,我剛剛(北京時間2017年3月28日下午1:41)看到的是93.6%,據說Facebook在自己的網站已經將React Fiber投入實戰,所以,React Fiber大勢所趨,是時候了解一下React Fiber了。
React Fiber是個什麼東西呢?官方的一句話解釋是「React Fiber是對核心演算法的一次重新實現」。這麼說似乎太虛無縹緲,所以還是要詳細說一下。
首先,不用太緊張,不要以為React Fiber的到來是一場大革命,實際上,對我們只是把React當做工具的開發者來說,很可能感覺不到有什麼功能變化。等到React v16發布的時候,我們修改package.json中的react版本號,重新npm install,一切就搞定了,然後我們就感覺到網頁性能更高了一些,如此而已。
當然,上面說的是「理想情況」,React Fiber對演算法的修改,還是會帶來一些功能邏輯的變化,後面會說。
為什麼Facebook要搞React Fiber呢?我們先要了解現在React(也就是直到目前最新的v15版本)的局限。
同步更新過程的局限
在現有React中,更新過程是同步的,這可能會導致性能問題。
當React決定要載入或者更新組件樹時,會做很多事,比如調用各個組件的生命周期函數,計算和比對Virtual DOM,最後更新DOM樹,這整個過程是同步進行的,也就是說只要一個載入或者更新過程開始,那React就以不破樓蘭終不還的氣概,一鼓作氣運行到底,中途絕不停歇。
表面上看,這樣的設計也是挺合理的,因為更新過程不會有任何I/O操作嘛,完全是CPU計算,所以無需非同步操作,的確只要一路狂奔就行了,但是,當組件樹比較龐大的時候,問題就來了。
假如更新一個組件需要1毫秒,如果有200個組件要更新,那就需要200毫秒,在這200毫秒的更新過程中,瀏覽器那個唯一的主線程都在專心運行更新操作,無暇去做任何其他的事情。想像一下,在這200毫秒內,用戶往一個input元素中輸入點什麼,敲擊鍵盤也不會獲得響應,因為渲染輸入按鍵結果也是瀏覽器主線程的工作,但是瀏覽器主線程被React占著呢,抽不出空,最後的結果就是用戶敲了按鍵看不到反應,等React更新過程結束之後,咔咔咔那些按鍵一下子出現在input元素里了。
這就是所謂的界面卡頓,很不好的用戶體驗。
現有的React版本,當組件樹很大的時候就會出現這種問題,因為更新過程是同步地一層組件套一層組件,逐漸深入的過程,在更新完所有組件之前不停止,函數的調用棧就像下圖這樣,調用得很深,而且很長時間不會返回。
因為JavaScript單線程的特點,每個同步任務不能耗時太長,不然就會讓程序不會對其他輸入作出相應,React的更新過程就是犯了這個禁忌,而React Fiber就是要改變現狀。
React Fiber的方式
破解JavaScript中同步操作時間過長的方法其實很簡單——分片。
把一個耗時長的任務分成很多小片,每一個小片的運行時間很短,雖然總時間依然很長,但是在每個小片執行完之後,都給其他任務一個執行的機會,這樣唯一的線程就不會被獨佔,其他任務依然有運行的機會。
React Fiber把更新過程碎片化,執行過程如下面的圖所示,每執行完一段更新過程,就把控制權交還給React負責任務協調的模塊,看看有沒有其他緊急任務要做,如果沒有就繼續去更新,如果有緊急任務,那就去做緊急任務。
維護每一個分片的數據結構,就是Fiber。
有了分片之後,更新過程的調用棧如下圖所示,中間每一個波谷代表深入某個分片的執行過程,每個波峰就是一個分片執行結束交還控制權的時機。
道理很簡單,但是React實現這一點卻不容易,要不然怎麼折騰了兩年多!
對具體數據結構原理感興趣的同學可以去看Lin Clark在React Conf 2017上的演講 (要翻牆的),本文中的介紹圖片也出自這個演講。
為什麼叫Fiber呢?
大家應該都清楚進程(Process)和線程(Thread)的概念,在計算機科學中還有一個概念叫做Fiber,英文含義就是「纖維」,意指比Thread更細的線,也就是比線程(Thread)控制得更精密的並發處理機制。
上面說的Fiber和React Fiber不是相同的概念,但是,我相信,React團隊把這個功能命名為Fiber,含義也是更加緊密的處理機制,比Thread更細。
說個題外話,很多年前,我聽一個前輩講課,他說到Fiber這麼一回事,我就問他:怎麼讓我的程序進入可以操控Fiber的狀態?前輩的回答是:你的程序真的需要用到Fiber嗎?如果現在的方法就能滿足需求,根本就不需要知道Fiber是什麼東西。
呵呵,前輩說的當時我還不大理解,後來越來越覺得前輩說得有道理,如果根本沒有必要用上一樣東西,那就不用也罷,不要因為那個東西酷就去用,不然很可能就是自找苦吃。
扯遠了,我想說的是,其實對大部分React使用者來說,也不用深究React Fiber是如何實現的,除非實現方式真的對我們的使用方式有影響,我們也不用要學會包子怎麼做的才吃包子對不對?
但是,React Fiber的實現改變還真的讓我們的代碼方式要做一些調整。
React Fiber對現有代碼的影響
理想情況下,React Fiber應該完全不影響現有代碼,但可惜並完全是這樣,要吃這個包子還真要知道一點這個包子怎麼做的,你如果不喜歡吃甜的就不要吃糖包子,對不對?
在React Fiber中,一次更新過程會分成多個分片完成,所以完全有可能一個更新任務還沒有完成,就被另一個更高優先順序的更新過程打斷,這時候,優先順序高的更新任務會優先處理完,而低優先順序更新任務所做的工作則會完全作廢,然後等待機會重頭再來。
因為一個更新過程可能被打斷,所以React Fiber一個更新過程被分為兩個階段(Phase):第一個階段Reconciliation Phase和第二階段Commit Phase。
在第一階段Reconciliation Phase,React Fiber會找出需要更新哪些DOM,這個階段是可以被打斷的;但是到了第二階段Commit Phase,那就一鼓作氣把DOM更新完,絕不會被打斷。
這兩個階段大部分工作都是React Fiber做,和我們相關的也就是生命周期函數。
以render函數為界,第一階段可能會調用下面這些生命周期函數,說是「可能會調用」是因為不同生命周期調用的函數不同。
- componentWillMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
下面這些生命周期函數則會在第二階段調用。
- componentDidMount
- componentDidUpdate
- componentWillUnmount
因為第一階段的過程會被打斷而且「重頭再來」,就會造成意想不到的情況。
比如說,一個低優先順序的任務A正在執行,已經調用了某個組件的componentWillUpdate函數,接下來發現自己的時間分片已經用完了,於是冒出水面,看看有沒有緊急任務,哎呀,真的有一個緊急任務B,接下來React Fiber就會去執行這個緊急任務B,任務A雖然進行了一半,但是沒辦法,只能完全放棄,等到任務B全搞定之後,任務A重頭來一遍,注意,是重頭來一遍,不是從剛才中段的部分開始,也就是說,componentWillUpdate函數會被再調用一次。
在現有的React中,每個生命周期函數在一個載入或者更新過程中絕對只會被調用一次;在React Fiber中,不再是這樣了,第一階段中的生命周期函數在一次載入和更新過程中可能會被多次調用!
使用React Fiber之後,一定要檢查一下第一階段相關的這些生命周期函數,看看有沒有邏輯是假設在一個更新過程中只調用一次,有的話就要改了。
我們挨個看一看這些可能被重複調用的函數。
componentWillReceiveProps,即使當前組件不更新,只要父組件更新也會引發這個函數被調用,所以多調用幾次沒啥,通過!
shouldComponentUpdate,這函數的作用就是返回一個true或者false,不應該有任何副作用,多調用幾次也無妨,通過!
render,應該是純函數,多調用幾次無妨,通過!
只剩下componentWillMount和componentWillUpdate這兩個函數往往包含副作用,所以當使用React Fiber的時候一定要重點看這兩個函數的實現。
怎麼試用React Fiber?
雖然React Fiber還沒有正式發布,現在你就可以試用React Fiber,用這種方式安裝react和react-dom
yarn add react@next react-dom@next
我試著將現有項目的React替換成React Fiber之後,代碼沒有任何改變,可以正常運行,說明我的應用里生命周期函數應該沒有假設只會被調用一次。
我相信當React Fiber正式發布的時候,會有更清晰詳盡的文檔,到時候我們再來詳細介紹。
歡迎關注這個專欄 進擊的React - 知乎專欄
更新:我開了一個Live專門介紹Fibeer《深入理解React v16新功能》
推薦閱讀:
※setState:這個API設計到底怎麼樣
※從零學習前端開發·CSS
※1.1 React 介紹
※React Render Array 性能大亂斗