從v16.3.0
開始以下三個生命週期鉤子被標記爲UNSAFE
。html
componentWillMountreact
componentWillRecievePropsgit
componentWillUpdategithub
究其緣由,有以下兩點:web
這三個鉤子常常被錯誤使用,而且如今出現了更好的替代方案(這裏指新增的getDerivedStateFromProps
與getSnapshotBeforeUpdate
)。微信
React
從Legacy
模式遷移到Concurrent
模式後,這些鉤子的表現會和以前不一致。數據結構
本文會從React
源碼的角度剖析這兩點。異步
同時,經過本文的學習你能夠掌握React異步狀態更新機制
的原理。編輯器
咱們先來探討第一點,這裏咱們以componentWillRecieveProps
舉例。學習
咱們常常在componentWillRecieveProps
內處理props
改變帶來的影響。有些同窗認爲這個鉤子會在每次props
變化後觸發。
真的是這樣麼?讓咱們看看源碼。
這段代碼出自updateClassInstance
方法:
if (
unresolvedOldProps !== unresolvedNewProps || oldContext !== nextContext ) { callComponentWillReceiveProps( workInProgress, instance, newProps, nextContext, ); } 複製代碼
你能夠從這裏看到這段源碼
其中callComponentWillReceiveProps
方法會調用componentWillRecieveProps
。
能夠看到,是否調用的關鍵是比較unresolvedOldProps
與 unresolvedNewProps
是否全等,以及context
是否變化。
其中unresolvedOldProps
爲組件上次更新時的props
,而unresolvedNewProps
則來自ClassComponent
調用this.render
返回的JSX
中的props
參數。
可見他們的引用
是不一樣的。因此他們全等比較
爲false
。
基於此緣由,每次父組件更新都會觸發當前組件的componentWillRecieveProps
。
想一想你是否也曾誤用過?
讓咱們再看第二個緣由:
React
從Legacy
模式遷移到Concurrent
模式後,這些鉤子的表現會和以前不一致。
咱們先了解下什麼是模式?不一樣模式有什麼區別?
從React15
升級爲React16
後,源碼改動如此之大,說React
被重構可能更貼切些。
正是因爲變更如此之大,使得一些特性在新舊版本React
中表現不一致,這裏就包括上文談到的三個生命週期鉤子。
爲了讓開發者能平穩從舊版本遷移到新版本,React
推出了三個模式:
legacy模式
-- 經過
ReactDOM.render
建立的應用會開啓該模式。這是當前
React
使用的方式。這個模式可能不支持一些新功能。
blocking模式
-- 經過
ReactDOM.createBlockingRoot
建立的應用會開啓該模式。開啓部分
concurrent
模式特性,做爲遷移到
concurrent
模式的第一步。
concurrent模式
-- 經過
ReactDOM.createRoot
建立的應用會開啓該模式。面向將來的開發模式。
你能夠從這裏看到不一樣模式的特性支持狀況
concurrent模式
相較咱們當前使用的legacy模式
最主要的區別是將同步的更新機制重構爲異步可中斷的更新。
接下來咱們來探討React
如何實現異步更新
,以及爲何異步更新
狀況下鉤子的表現和同步更新
不一樣。
咱們能夠用代碼版本控制
類比更新機制
。
在沒有代碼版本控制
前,咱們在代碼中逐步疊加功能。一切看起來井井有理,直到咱們遇到了一個緊急線上bug(紅色節點)。
爲了修復這個bug,咱們須要首先將以前的代碼提交。
在React
中,全部經過ReactDOM.render
建立的應用都是經過相似的方式更新狀態。
即全部更新
同步執行,沒有優先級
概念,新來的高優更新
(紅色節點)也須要排在其餘更新
後面執行。
當有了代碼版本控制
,有緊急線上bug須要修復時,咱們暫存當前分支的修改,在master分支
修復bug並緊急上線。
bug修復上線後經過git rebase
命令和開發分支
鏈接上。開發分支
基於修復bug的版本繼續開發。
在React
中,經過ReactDOM.createBlockingRoot
和ReactDOM.createRoot
建立的應用在任務未過時狀況下會採用異步的方式更新狀態。
高優更新
(紅色節點)中斷正在進行中的低優更新
(藍色節點),先完成渲染流程。
待高優更新
完成後,低優更新
基於高優更新
的部分
或者完整
結果從新更新。
在React
源碼中,每次發起更新
都會建立一個Update
對象,同一組件的多個Update
(如上圖所示的A -> B -> C)會以鏈表
的形式保存在updateQueue
中。
首先了解下他們的數據結構
。
Update
有不少字段,當前咱們關注以下三個字段:
const update: Update<*> = { // ...省略當前不須要關注的字段 lane, payload: null, next: null }; 複製代碼
Update
由createUpdate
方法返回,你能夠從這裏看到createUpdate
的源碼
紅色
節點與
藍色
節點的區別。
this.setState
建立的
更新
,
payload
爲
this.setState
的傳參。
Update
鏈接造成鏈表。
updateQueue
結構以下:
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState, firstBaseUpdate: null, lastBaseUpdate: null, shared: { pending: null, }, // 其餘參數省略... }; 複製代碼
UpdateQueue
由initializeUpdateQueue
方法返回,你能夠從這裏看到initializeUpdateQueue
的源碼
baseState
:
更新
基於哪一個
state
開始。上圖中
版本控制
的例子中,高優bug修復後提交
master
,其餘
commit
基於
master
分支繼續開發。這裏的
master
分支就是
baseState
。
firstBaseUpdate
與
lastBaseUpdate
:
更新
基於哪一個
Update
開始,由
firstBaseUpdate
開始到
lastBaseUpdate
結束造成鏈表。這些
Update
是在上次
更新
中因爲
優先級
不夠被留下的,如圖中
A B C
。
shared.pending
:本次更新的單或多個
Update
造成的鏈表。
其中baseUpdate
+ shared.pending
會做爲本次更新須要執行的Update
。
瞭解了數據結構
,接下來咱們模擬一次異步中斷更新
,來揭示本文探尋的祕密 —— componentWillXXX
爲何UNSAFE
。
在某個組件updateQueue
中存在四個Update
,其中字母
表明該Update
要更新的字母,數字
表明該Update
的優先級,數字越小優先級
越高。
baseState = '';
A1 - B2 - C1 - D2 複製代碼
首次渲染時,優先級
1。B D
優先級不夠被跳過。
爲了保證更新
的連貫性,第一個被跳過的Update
(B
)及其後面全部Update
會做爲第二次渲染的baseUpdate
,不管他們的優先級
高低,這裏爲B C D
。
baseState: ''
Updates: [A1, C1] Result state: 'AC' 複製代碼
接着第二次渲染,優先級
2。
因爲B
在第一次渲染時被跳過,因此在他以後的C
形成的渲染結果不會體如今第二次渲染的baseState
中。因此baseState
爲A
而不是上次渲染的Result state AC
。這也是爲了保證更新
的連貫性。
baseState: 'A'
Updates: [B2, C1, D2] Result state: 'ABCD' 複製代碼
咱們發現,C
同時出如今兩次渲染的Updates
中,他表明的狀態
會被更新兩次。
若是有相似的代碼:
componentWillReceiveProps(nextProps) {
if (!this.props.includes('C') && nextProps.includes('C')) { // ...do something } } 複製代碼
則頗有可能被調用兩次,這與同步更新
的React
表現不一致!
基於以上緣由,componentWillXXX
被標記爲UNSAFE
。
因爲篇幅有限,本次咱們只聚焦了React
源碼的冰山一角。
若是想深刻學習React
源碼,在此向你推薦開源
、嚴謹
、易懂
的React源碼電子書 —— React技術揭祕
同時能夠加微信(iamkasong)拉你進源碼交流羣
和小夥伴們一塊兒交流React源碼
。
React Contributor
在線答疑。