当前位置: 首页 > 科技观察

从源码上搞清楚UseEffect的第二个参数是如何处理的

时间:2023-03-12 19:58:34 科技观察

useEffect是一个常用的hook,支持两个参数,第一个参数是回调函数,第二个参数是依赖。当第二个参数为null或undefined时,每次执行render都会执行回调函数,当参数为数组时,只有在依赖发生变化时才会执行。这些我们都很熟悉,但是它是如何实现的呢?让我们从源码中寻找答案。useEffect的第二个参数我们先来试试当第二个参数传入undefined,一个空数组,一个有依赖的数组时的效果。准备一段这样的代码:import{useEffect,useRef,useState}from'react';functionDong(){constref=useRef(1);const[,setState]=useState();useEffect(()=>{console.log(111);});useEffect(()=>{console.log(222);},[]);useEffect(()=>{console.log(333);},[ref.current]);useEffect(()=>{setInterval(()=>{setState([]);},1000);setTimeout(()=>{ref.current=2;},3000);},[]);return

dong
;}我们写了三个useEffects,第二个参数undefined,[],还有一个依赖数组,回调函数分别打印111、222、333。然后useState声明一个state,用setInterval定时修改,这样render就可以一直触发。另外一个对象是用useRef声明的,其特点是每次render返回的都是同一个对象,我们在2s后用setTimeout修改它的值。执行结果应该很容易想到:每次都会打印111,因为第二个参数未定义。222只打印一次,因为第二个参数是[]。333打印了两次,因为第二个参数有一个依赖关系,这个依赖关系每2s会变化一次。这些我们都很熟悉,但为什么会这样呢?看源码:useEffect相关源码reacthooks的原理写在上一篇,我们再过一遍:jsx编译生成render函数,执行返回vdom,但是为了提高性能方面,React16引入了Fiber架构,会先将vdom转换为fiber,再更新为dom。vdom转换为fiber的过程称为reconcile,更新为dom的过程称为commit。协调过程是可中断的,需要一个时间表。Hooks也是基于fiber实现的。它在fiber节点上维护了一个链表(memorizedState属性)来存储数据。每个钩子从相应的链表元素访问自己的数据。比如上面组件的6个hook对应fiber节点上memorizedState链表的6个元素:每个hook访问对应链表元素上的数据。这个链表有一个构建过程叫做mount,后面只需要update,所以每个hook的实现会分为mount和update两个阶段。我们看useEffect相关的源码:同样分为mountEffect和updateEffect两个函数,最后访问hook.memorizedState中的元素。这是钩子的一般原理。第二个参数对应的是deps,它是如何判断是否更新的呢?重点看一下这个逻辑:deps是新传入的参数,如果没有定义,会被认为是null。hook.memorizedState.deps获取之前的deps。然后会比较新旧dep,如果返回true,则执行effect。比较的逻辑在函数areHookInputsEqual中:如果prevDeps为null,则直接返回false,这也是为什么useEffect的第二个参数传入undefined或null时,会执行effect函数的原因。否则,将比较新旧deps数组中的每个元素,如果有差异则返回false。上面已经解释了deps数组传入undefined、[]、[dep]时effect执行的不同情况。其实还有一种情况会导致effect的执行,就是上面的逻辑:热更新时,即使依赖没有变化,也需要重新执行effect。这是由ignorePreviousDependencies变量控制的。这个估计很多人不知道,因为热更新是通过工具来实现的。useEffect的第二个参数的处理机制我们已经从源码层面解释清楚了。其实useCallback和useMemo的deps参数处理逻辑也是一样的,源码类似:总结useEffect传入undefined时的第二个参数,[],[a,b,c],执行效果不同的是,undefined每次都会执行,而依赖数组只会在依赖发生变化时执行,而空数组只会执行一次。我们从源码层面解释了原因:hooks访问fiber节点的memorizedState属性上的数据,会组织一个hook一一对应的链表。构建这个链表的阶段叫做mount,后面只需要update,所以所有hooks的实现分为mountXxx和updateXxx两部分。useEffect在memorizedState上比较新引入的dep和之前存在的dep来决定更新时是否执行effect回调。它是这样做的:当dep为null时(undefined也会被当作null),判断为unequal。如果是热更新,则判断为不等。否则,比较数组的每个依赖项是否相等。只要新旧dep不相等,就会执行effect。useCallback和useMemo的deps处理也是一样的。我们从源码层面明确了deps参数的处理机制。