關於useEffect的一切

做爲React開發者,你能答上以下兩個問題麼:web

  1. 對於以下函數組件:
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/>時控制檯的打印順序是?數組

  1. 以下兩個回調函數的調用時機相同麼?
// 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的源碼能夠拆分爲三塊:編輯器

  • 調度器:調度更新
  • 協調器:決定更新的內容
  • 渲染器:將更新的內容渲染到視圖中

其中,只有渲染器會執行渲染視圖操做。函數

對於瀏覽器環境來講,只有渲染器會執行相似appendChildinsertBefore這樣的DOM操做。flex

協調器如何決定更新的內容呢?url

答案是:他會爲須要更新的內容對應的fiber(能夠理解爲虛擬DOM)打上標記。

這些被打標記的fiber會造成一條鏈表effectList

渲染器會遍歷effectList,執行標記對應的操做。

  • 好比Placement標記對應插入DOM

  • 好比Update標記對應更新DOM屬性

useEffect也遵循一樣的工做原理:

  1. 觸發更新時,FunctionComponent被執行,執行到useEffect時會判斷他的第二個參數deps是否有變化。

  2. 若是deps變化,則useEffect對應FunctionComponentfiber會被打上Passive(即:須要執行useEffect)的標記。

  3. 渲染器中,遍歷effectList過程當中遍歷到該fiber時,發現Passive標記,則依次執行該useEffectdestroy(即useEffect回調函數的返回值函數)與create(即useEffect回調函數)。

其中,前兩步發生在協調器中。

因此,effectList構建的順序就是useEffect的執行順序。

effectList

協調器的工做流程是使用遍歷實現的遞歸。因此能夠分爲兩個階段。

咱們知道,是從根節點向下一直到葉子節點,是從葉子節點一路向上到根節點。

effectList的構建發生在階段。因此,effectList的順序也是從葉子節點一路向上。

useEffect對應fiber做爲effectList中的一個節點,他的調用邏輯也遵循的流程。

如今,咱們有充足的知識回答第一個問題:

因爲階段是從ChildParentApp,因此相應effectList也是一樣的順序。

因此useEffect回調函數執行也是一樣的順序。

不要用生命週期鉤子類比hook

咱們在初學hook時,會用ClassComponent的生命週期鉤子類比hook的執行時機。

即便官網也是這樣教學的。

可是,從上文咱們已經知道,React的執行遵循:

調度 -- 協調 -- 渲染

渲染相關工做原理是按照:

構建effectList -- 遍歷effectList執行對應操做

整個過程都和生命週期鉤子沒有關係。

事實上生命週期鉤子只是附着在這一流程上的鉤子函數。

因此,更好的方式是從React運行流程來理解useEffect的執行時機。

渲染

按照流程,effectList會在渲染器中被處理。

對於useEffect來講,遍歷effectList時,會找到的全部包含Passive標記的fiber

依次執行對應useEffectdestroy

全部destroy執行完後,再依次執行全部create

整個過程是在頁面渲染後異步執行的。

回答第二個問題:

若是useEffectdeps[],因爲deps不會改變,對應fiber只會在mount時被標記Passive

這點是相似componentDidMount的。

可是,處理Passive effect是在渲染完成後異步執行,而componentDidMount是在渲染完成後同步執行,因此他們是不一樣的。

useEffect與useLayoutEffect

componentDidMount更相似的是useLayoutEffect,他會在渲染完成後同步執行。

這裏提供個在線Demo[1],你能夠將Demo中的useLayoutEffect替換爲useEffect,看看他們的區別。

總結

經過本文,咱們瞭解了useEffect的完整執行過程。

本系列文章接下來會繼續以實例 + 源碼的方式,解讀業務中常常使用的React特性。

點擊閱讀原文,開源電子書輕鬆學懂React源碼

參考資料

[1]

在線Demo: https://code.h5jun.com/haxufe/edit?js,output



本文分享自微信公衆號 - 牧碼的星星(gh_0d71d9e8b1c3)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索