关于useEffect的一切
作为React
开发者,你能答上如下两个问题么:
- 对于如下函数组件:
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
工作流程
本文接下来将深入源码,带你了解这些知识。
这,就是关于useEffect
的一切。
useEffect的执行顺序
React
的源码可以拆分为三块:
- 调度器:调度更新
- 协调器:决定更新的内容
- 渲染器:将更新的内容渲染到视图中
其中,只有渲染器
会执行渲染视图操作。
对于浏览器环境来说,只有渲染器
会执行类似appendChild
、insertBefore
这样的DOM
操作。
协调器
如何决定更新的内容呢?
答案是:他会为需要更新的内容对应的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
特性。
参考资料
[1]
在线Demo: https://code.h5jun.com/haxufe/edit?js,output
- SEVERE: Error configuring application listener of class org.springframework.web.context.ContextLoade
- TCP/IP(一)之开启计算机网络之路
- JSON入门指南--客户端处理JSON
- mysql5.7 ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
- TCP/IP中你不得不知的十大秘密
- Java Web开发学习之路2012版
- TortoiseSVN客户端使用的2个配置问题
- JavaWeb(二)会话管理之细说cookie与session
- 概率论09 期望
- Javascript中数组的sort()和reverse()方法
- CentOS6.5开放端口,配置防火墙
- JavaWeb(一)Servlet中乱码解决与转发和重定向的区别
- Java魔法堂:四种引用类型、ReferenceQueue和WeakHashMap
- Javascript中数组的使用
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- laravel 配置路由 api和web定义的路由的区别详解
- Flutter 实现网易云音乐字幕的代码
- Yii框架通过请求组件处理get,post请求的方法分析
- PHP实现单文件、多个单文件、多文件上传函数的封装示例
- Android自定义控件单位尺寸实现代码
- Android中socket通信的简单实现
- Thinkphp5框架使用validate实现验证功能的方法
- Android通过Java sdk的方式接入OpenCv的方法
- php+js实现的无刷新下载文件功能示例
- Android如何获取视频首帧图片
- PHP单文件上传原理及上传函数的封装操作示例
- php中错误处理操作实例分析
- Android 百度地图定位实现仿钉钉签到打卡功能的完整代码
- Thinkphp5框架实现图片、音频和视频文件的上传功能详解
- Android使用Opengl录像时添加水印