关于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
- 生产环境sql语句调优实战第四篇(r2笔记41天)
- 生产环境sql语句调优实战第五篇(r2笔记41天)
- python实现逻辑logistic回归:预测病马的死亡率
- 开发 | 图片数据集太少?看我七十二变,Keras Image Data Augmentation 各参数详解
- linux过滤空文件的命令总结(r2笔记40天)
- shell脚本自动化采集性能sql(r2笔记39天)
- R语言与点估计学习笔记(EM算法与Bootstrap法)
- 开发 | 为个人深度学习机器选择合适的配置
- 阿里音乐流行趋势预测竞赛数据清洗整合——纯python
- 生产环境sql语句调优实战第二篇(r2第38天)
- 生产环境sql语句调优实战第三篇(r2笔记38天)
- 简单易学的机器学习算法——K-Means算法
- 通过shell脚本定位性能sql和生成报告(r2笔记37天)
- VXFS启用异步IO导致的严重问题(r2笔记56天)
- 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 数组属性和方法
- 12 . Kubernetes之Statefulset 和 Operator
- 01 . SaltStack部署配置及简单应用
- 02 . SaltStack高级用法(Python API)
- 小加载动画
- 日志收集工具简单对比
- [蓝桥杯][2013年第四届真题]幸运数
- 04 . Filebeat简介原理及配置文件和一些案例
- 05 . ELK Stack+Redis日志收集平台
- python开发【第一篇】
- 内置函数--bin() oct() int() hex()
- 08 . Prometheus+Grafana监控haproxy+rabbitmq
- 内置函数值 -- chr() ord() -- 字符和ascii的转换
- python内置函数-compile()
- 02 . Shell变量和逻辑判断及循环使用
- Python内置函数(21)——filter