前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于useEffect的一切

关于useEffect的一切

作者头像
公众号@魔术师卡颂
发布2020-09-01 11:31:45
1K0
发布2020-09-01 11:31:45
举报
文章被收录于专栏:魔术师卡颂魔术师卡颂

作为React开发者,你能答上如下两个问题么:

  1. 对于如下函数组件:
代码语言:javascript
复制
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. 如下两个回调函数的调用时机相同么?
代码语言:javascript
复制
// componentDidMount生命周期钩子
class App extends React.Component {
  componentDidMount() {
    console.log('hello');
  }
}

// 依赖为[]的useEffect
useEffect(() => {
  console.log('hello');
}, [])

答案:

代码语言:javascript
复制
?向右滑动翻看答案                                                                     1. child -> parent -> app
                                                                                    2. 不同                                              

其实,这两个问题分别考察的是:

  • useEffect的执行顺序
  • useEffect如何介入React工作流程

本文接下来将深入源码,带你了解这些知识。

这,就是关于useEffect的一切。

useEffect的执行顺序

React的源码可以拆分为三块:

  • 调度器:调度更新
  • 协调器:决定更新的内容
  • 渲染器:将更新的内容渲染到视图中

其中,只有渲染器会执行渲染视图操作。

对于浏览器环境来说,只有渲染器会执行类似appendChildinsertBefore这样的DOM操作。

协调器如何决定更新的内容呢?

答案是:他会为需要更新的内容对应的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的执行遵循:

代码语言:javascript
复制
调度 -- 协调 -- 渲染

渲染相关工作原理是按照:

代码语言:javascript
复制
构建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特性。

参考资料

[1]

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 魔术师卡颂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • useEffect的执行顺序
  • effectList
  • 不要用生命周期钩子类比hook
  • 渲染
  • useEffect与useLayoutEffect
  • 总结
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档