做爲React
開發者,你能答上以下兩個問題麼:web
-
對於以下函數組件:
function Child() {
useEffect(() => {
console.log('child');
}, [])
return <p>hello</p>;
}
function Parent() {
useEffect(() => {
console.log('parent');
}, [])
return <Child/>;
}
function App() {
useEffect(() => {
console.log('app');
}, [])
return <Parent/>;
}
渲染<App/>
時控制檯的打印順序是?數組
-
以下兩個回調函數的調用時機相同麼?
// componentDidMount生命週期鉤子
class App extends React.Component {
componentDidMount() {
console.log('hello');
}
}
// 依賴爲[]的useEffect
useEffect(() => {
console.log('hello');
}, [])
答案:瀏覽器
👉向右滑動翻看答案 1. child -> parent -> app
2. 不一樣
其實,這兩個問題分別考察的是:微信
-
useEffect
的執行順序 -
useEffect
如何介入React
工做流程
本文接下來將深刻源碼,帶你瞭解這些知識。app
這,就是關於useEffect
的一切。異步
useEffect的執行順序
React
的源碼能夠拆分爲三塊:編輯器
-
調度器:調度更新 -
協調器:決定更新的內容 -
渲染器:將更新的內容渲染到視圖中
其中,只有渲染器
會執行渲染視圖操做。函數
對於瀏覽器環境來講,只有渲染器
會執行相似appendChild
、insertBefore
這樣的DOM
操做。flex
協調器
如何決定更新的內容呢?url
答案是:他會爲須要更新的內容對應的fiber
(能夠理解爲虛擬DOM
)打上標記。
這些被打標記的fiber
會造成一條鏈表effectList
。
渲染器
會遍歷effectList
,執行標記對應的操做。
-
好比
Placement
標記對應插入DOM
-
好比
Update
標記對應更新DOM
屬性
useEffect
也遵循一樣的工做原理:
-
觸發更新時,
FunctionComponent
被執行,執行到useEffect
時會判斷他的第二個參數deps
是否有變化。 -
若是
deps
變化,則useEffect
對應FunctionComponent
的fiber
會被打上Passive
(即:須要執行useEffect)的標記。 -
在
渲染器
中,遍歷effectList
過程當中遍歷到該fiber
時,發現Passive
標記,則依次執行該useEffect
的destroy
(即useEffect
回調函數的返回值函數)與create
(即useEffect
回調函數)。
其中,前兩步發生在協調器
中。
因此,effectList
構建的順序就是useEffect
的執行順序。
effectList
協調器
的工做流程是使用遍歷
實現的遞歸
。因此能夠分爲遞
與歸
兩個階段。
咱們知道,遞
是從根節點向下一直到葉子節點,歸
是從葉子節點一路向上到根節點。
effectList
的構建發生在歸
階段。因此,effectList
的順序也是從葉子節點一路向上。
useEffect
對應fiber
做爲effectList
中的一個節點,他的調用邏輯也遵循歸
的流程。
如今,咱們有充足的知識回答第一個問題:
因爲歸
階段是從Child
到Parent
到App
,因此相應effectList
也是一樣的順序。
因此useEffect
回調函數執行也是一樣的順序。
不要用生命週期鉤子類比hook
咱們在初學hook
時,會用ClassComponent
的生命週期鉤子類比hook
的執行時機。
即便官網也是這樣教學的。
可是,從上文咱們已經知道,React
的執行遵循:
調度 -- 協調 -- 渲染
渲染相關工做原理是按照:
構建effectList -- 遍歷effectList執行對應操做
整個過程都和生命週期鉤子
沒有關係。
事實上生命週期鉤子
只是附着在這一流程上的鉤子函數。
因此,更好的方式是從React
運行流程來理解useEffect
的執行時機。
渲染
按照流程,effectList
會在渲染器
中被處理。
對於useEffect
來講,遍歷effectList
時,會找到的全部包含Passive
標記的fiber
。
依次執行對應useEffect
的destroy
。
全部destroy
執行完後,再依次執行全部create
。
整個過程是在頁面渲染後異步執行的。
回答第二個問題:
若是useEffect
的deps
爲[]
,因爲deps
不會改變,對應fiber
只會在mount
時被標記Passive
。
這點是相似componentDidMount
的。
可是,處理Passive
effect
是在渲染完成後異步執行,而componentDidMount
是在渲染完成後同步執行,因此他們是不一樣的。
useEffect與useLayoutEffect
與componentDidMount
更相似的是useLayoutEffect
,他會在渲染完成後同步執行。
這裏提供個在線Demo[1],你能夠將Demo
中的useLayoutEffect
替換爲useEffect
,看看他們的區別。
總結
經過本文,咱們瞭解了useEffect
的完整執行過程。
本系列文章接下來會繼續以實例 + 源碼的方式,解讀業務中常常使用的React
特性。
點擊閱讀原文,開源電子書輕鬆學懂React源碼
參考資料
在線Demo: https://code.h5jun.com/haxufe/edit?js,output
本文分享自微信公衆號 - 牧碼的星星(gh_0d71d9e8b1c3)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。