深入理解 React useLayoutEffect 和 useEffect 的执行时机

更新日期: 2020-04-08 阅读: 2.2k 标签: React

我们先看下 react 官方文档对这两个 hook 的介绍,建立个整体认识

useEffect(create, deps):

该 Hook 接收一个包含命令式、且可能有副作用代码函数。在函数组件主体内(这里指在 React 渲染阶段)改变 dom、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。

useLayoutEffect(create, deps):

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

注意加粗的字段,React 官方的文档其实把两个 hook 的执行时机说的很清楚,下面我们深入到 react 的执行流程中来理解下


问题

useEffect 和 useLayoutEffect 的区别?

useEffect 和 useLayoutEffect 哪一个与 componentDidMount,componentDidUpdate 的是等价的?

useEffect 和 useLayoutEffect 哪一个与 componentWillUnmount 的是等价的?

为什么建议将修改 DOM 的操作里放到 useLayoutEffect 里,而不是 useEffect?


流程

react 在 diff 后,会进入到 commit 阶段,准备把虚拟 DOM 发生的变化映射到真实 DOM 上

在 commit 阶段的前期,会调用一些生命周期方法,对于类组件来说,需要触发组件的 getSnapshotBeforeUpdate 生命周期,对于函数组件,此时会调度 useEffect 的 create destroy 函数

注意是调度,不是执行。在这个阶段,会把使用了 useEffect 组件产生的生命周期函数入列到 React 自己维护的调度队列中,给予一个普通的优先级,让这些生命周期函数异步执行

// 可以近似的认为,React 做了这样一步,实际流程中要复杂的多

setTimeout(() => {
      const preDestory = element.destroy;
      if (!preDestory) prevDestroy();
      const destroy = create();
      element.destroy= destroy;
}, 0);

随后,就到了 React 把虚拟 DOM 设置到真实 DOM 上的阶段,这个阶段主要调用的函数是 commitWork,commitWork 函数会针对不同的 fiber 节点调用不同的 DOM 的修改方法,比如文本节点和元素节点的修改方法是不一样的。

commitWork 如果遇到了类组件的 fiber 节点,不会做任何操作,会直接 return,进行收尾工作,然后去处理下一个节点,这点很容易理解,类组件的 fiber 节点没有对应的真实 DOM 结构,所以就没有相关操作

但在有了 hooks 以后,函数组件在这个阶段,会同步调用上一次渲染时 useLayoutEffect(create, deps) create 函数返回的 destroy 函数

注意一个节点在 commitWokr 后,这个时候,我们已经把发生的变化映射到真实 DOM 上了

但由于 JS 线程和浏览器渲染线程是互斥的,因为 JS 虚拟机还在运行,即使内存中的真实 DOM 已经变化,浏览器也没有立刻渲染到屏幕上

此时会进行收尾工作,同步执行对应的生命周期方法,我们说的componentDidMount,componentDidUpdate 以及 useLayoutEffect(create, deps) 的 create 函数都是在这个阶段被同步执行

对于 react 来说,commit 阶段是不可打断的,会一次性把所有需要 commit 的节点全部 commit 完,至此 react 更新完毕,JS 停止执行

浏览器把发生变化的 DOM 渲染到屏幕上,到此为止 react 仅用一次回流、重绘的代价,就把所有需要更新的 DOM 节点全部更新完成

浏览器渲染完成后,浏览器通知 react 自己处于空闲阶段,react 开始执行自己调度队列中的任务,此时才开始执行 useEffect(create, deps) 的产生的函数


几个问题

useEffect 和 useLayoutEffect 的区别?

useEffect 在渲染时是异步执行,并且要等到浏览器将所有变化渲染到屏幕后才会被执行。

useLayoutEffect 在渲染时是同步执行,其执行时机与 componentDidMount,componentDidUpdate 一致


对于 useEffect 和 useLayoutEffect 哪一个与 componentDidMount,componentDidUpdate 的是等价的?

useLayoutEffect,因为从源码中调用的位置来看,useLayoutEffect的 create 函数的调用位置、时机都和 componentDidMount,componentDidUpdate 一致,且都是被 React 同步调用,都会阻塞浏览器渲染。


useEffect 和 useLayoutEffect 哪一个与 componentWillUnmount 的是等价的?

同上,useLayoutEffect 的 detroy 函数的调用位置、时机与 componentWillUnmount 一致,且都是同步调用。useEffect 的 detroy 函数从调用时机上来看,更像是 componentDidUnmount (注意React 中并没有这个生命周期函数)。


为什么建议将修改 DOM 的操作里放到 useLayoutEffect 里,而不是 useEffect?

可以看到在流程9/10期间,DOM 已经被修改,但但浏览器渲染线程依旧处于被阻塞阶段,所以还没有发生回流、重绘过程。由于内存中的 DOM 已经被修改,通过 useLayoutEffect 可以拿到最新的 DOM 节点,并且在此时对 DOM 进行样式上的修改,假设修改了元素的 height,这些修改会在步骤 11 和 react 做出的更改一起被一次性渲染到屏幕上,依旧只有一次回流、重绘的代价。

如果放在 useEffect 里,useEffect 的函数会在组件渲染到屏幕之后执行,此时对 DOM 进行修改,会触发浏览器再次进行回流、重绘,增加了性能上的损耗。

来自:https://www.cnblogs.com/iheyunfei/archive/2020/06/08/13065047.html


本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/9271

相关推荐

Gatsby.js_一款基于React.js静态站点生成工具

Gatsby能快速的使用 React 生态系统来生成静态网站,可以结合React Component、Markdown 和服务端渲染来完成静态网站生成让他更强大。

解决vscode 开发react 导入绝对路径 无法跳转的问题

相对路径可正常跳转,但是在webpack配置alias使用绝对路径后无法跳转.解决办法:需要添加一个jsconfig文件,如下:

react router中页面传值的三种方法

这篇文章主要介绍React Router定义路由之后如何传值,有关React和React Router 。react router中页面传值的三种方法:props.params、query、state

React常用hook的优化useEffect浅比较

先说说react原版的useEffect使用起来不便的地方,这里的effect每次更新都会执行,因为第三个参数一直是不等的,第二是在deps依赖很多的时候是真的麻烦

React 监听页面滚动,界面动态显示

当页面滚动时,如何动态切换布局/样式, 添加滚动事件的监听/注销

React + Webpack 构建打包优化

React 相关的优化:使用 babel-react-optimize 对 React 代码进行优化,检查没有使用的库,去除 import 引用,按需打包所用的类库,比如 lodash 、echarts 等.Webpack 构建打包存在的问题两个方面:构建速度慢,打包后的文件体积过大

react-router v4 按需加载的配置方法

在react项目开发中,当访问默认页面时会一次性请求所有的js资源,这会大大影响页面的加载速度和用户体验。所以添加按需加载功能是必要的,以下是配置按需加载的方法

React事件处理函数必须使用bind(this)的原因

学习React的过程中发现调用函数的时候必须使用bind(this),之后直接在class中声明函数即可正常使用,但是为什么呢,博主进行了一番查阅,总结如下。

grpc-web与react的集成

使用create-react-app脚手架生成react相关部分,脚手架内部会通过node自动起一个客户端,然后和普通的ajax请求一样,和远端服务器进行通信,只不过这里采用支持rpc通信的grpc-web来发起请求,远端采用docker容器的node服务器,node服务器端使用envoy作为代理

react中的refs属性的使用方法

React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!