关于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源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索