一文带你了解react中的useEffect

本文将以react 18.1.0进行讲解

相信用过react的同学对useEffect应该十分熟悉, useEffect和useState可以说是我们在日常开发中最常用的hook。工欲善其事,必先利其器,在react中useEffect就是我们的武器,因此了解它的运行原理对我们的日常开发是十分有必要的。先一起来看下下面的例子:建议你先暂停几秒钟,思考自己的答案。实例在线地址:codesandbox.io/s/spring-ha...

接下来,我们来验证你的猜想是否正确。react首次渲染,执行结果如下。

先说几个前置小知识, react的整个流程主要有render阶段、commit阶段。render阶段主要生成我们的整个fiber树,并对其完成一些标记,例如插入、删除、更新、副作用(例如useEffect)等。commit阶段主要是和宿主环境进行交互,对于我们前端来说,宿主环境通常是浏览器。useEffect函数的执行,通常是在commit阶段完成之后执行的。

fiber节点中的几个重要字段return(父节点)、child(父节点的第一个子节点)、slibing(兄弟节点)、flags(标记各种副作用)、subtreeFlags(所有子节点标记的flags的集合)。updateQueue中lastEffect存储useEffect的相关参数, create为useEffect的第一个参数因此它是一个函数,deps为useEffect的第二个参数因此它是一个数组或null。

Child1组件的fiber节点示例。

目前我们整个fiber的结构如下:

首次运行时,所有包含useEffect的函数组件对应的fiber节点都会被标记副作用对应字段为flags,同时会向父节点冒泡作用在subtreeFlags字段。

好,言归正传,我们来分析上面例子的运行结果。 在首次渲染时候,我们主要关注 commitPassiveMountEffects函数,commitPassiveMountEffects中finishedWork是FiberRootNode的current属性,也就是HostRootFiber。也许你对这些名词还很陌生,没关系根据上面的fiber结构示意图,我们知道它目前在我们整个fiber架构中的位置即可。

根据上述代码我们可以知道react是深度遍历优先。接下来我们将一步一步进行分析:

在遍历过程中nextEffect的初始值为HostFiber。后面经过迭代依次为HostFiber-> 满足条件b1 -> App -> 满足条件b1 ------> Childb1 ------>.....满足条件b1 ------------> child1(text节点) 此时child1(text节点)已经没有child节点,因此进入commitPassiveMountEffects_complete方法。

commitPassiveMountEffects_complete中nextEffect的初始值为child1(text节点)对应的fiber节点。单纯的文本节点的flags是不会标记 Passive的,只有函数组件才会标记。因此会进入逻辑3,即nextEffect此时为Fragment。此时我们会回到commitPassiveMountEffects_begin方法中,nextEffect的变化为Fragment->满足条件b1-> Chlid2 -------> 满足条件b1 ------> div(id为child2)------> commitPassiveMountEffects_complete方法 ------> 满足条件4------> Child2------> 满足条件1------> 进入 commitPassiveMountOnFiber方法, 在commitPassiveMountOnFiber方法中,我们执行Child2中的useEffect的第一个参数即create,此时控制台会打印出 child2

commitPassiveMountOnFiber执行完成后,会回到commitPassiveMountEffects_complete中继续执行,此时nextEffect为Child2------> 满足条件3 ------> Child6------> 进入commitPassiveMountEffects_begin------> 满足条件b1 ------> div(id为child6)------>满足条件b2进入commitPassiveMountEffects_complete--------->满足条件4------> Child6--------->满足条件1------------>进入 commitPassiveMountOnFiber方法执行Child6中的useEffect的第一个参数即create,此时控制台会打印出 child6------> 回到commitPassiveMountEffects_complete函数------>满足条件4------> Fragment ------> ...满足条件4------> Child1 ------>满足条件1打印child1------> 满足条件3------> Child3回到commitPassiveMountEffects_begin方法------>满足条件b1------>div(id为child3)------> 满足条件b1------>child3(text节点)------>满足条件b2------>进入commitPassiveMountEffects_complete------>满足条件3------> Child4------> 回到commitPassiveMountEffects_begin------> ...满足条件b1 ------> Child4(text节点) ------> 满足条件b2 ------>commitPassiveMountEffects_complete------> 满足条件3------>Child5------> 回到commitPassiveMountEffects_begin------> 满足条件b1------> div(id为child5)------> 满足条件b2------>commitPassiveMountEffects_complete------> 满足条件4------> Child5------>满足条件1打印child5------>满足条件4------> div(id为child4)------> 满足条件4------>Child4------> 满足条件1打印child4------> 满足条件4------>div(id为child3)------> 满足条件4------> Child3------>满足条件1打印child3------> 满足条件4------> App------>满足条件1打印app------>满足条件4------>HostRoot------> 满足条件3终止。

到此我们所有的effect都已经执行完毕了。打印顺序分别为child2、child6、child1、child5、child4、child3、app。

相关推荐
GISer_Jing10 小时前
React核心功能详解(一)
前端·react.js·前端框架
FØund40412 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
疯狂的沙粒13 小时前
如何在 React 项目中应用 TypeScript?应该注意那些点?结合实际项目示例及代码进行讲解!
react.js·typescript
鑫宝Code14 小时前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
沉默璇年1 天前
react中useMemo的使用场景
前端·react.js·前端框架
红绿鲤鱼1 天前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
loey_ln1 天前
FIber + webWorker
javascript·react.js
zhenryx1 天前
前端-react(class组件和Hooks)
前端·react.js·前端框架
water1 天前
Nextjs系列——新版本路由的使用
前端·javascript·react.js
老码沉思录1 天前
React Native 全栈开发实战班 - 性能与调试之打包与发布
javascript·react native·react.js