实现一个 Mini React:核心功能详解 - 重构useEffect的实现

126 阅读3分钟

xdm,又要到饭了,又更新代码了!

总结一下上一篇完成的内容,

  1. 改进了useState的实现, 解决了批量更新的性能提升。

有兴趣的可以点这里查看重构useState的实现 - 批量更新实现 (2)

这一篇,我们将会重构之前的useEffect, 配合fiber结构,从底层源码理解useEffect的运行机制,以及更新时机。

开始重构之前,我们先展示一下之前完成的useEffect代码,

const hooks = []
let currentHook = 0

function useEffect(effect, deps) {
   const oldDeps = hooks[currentHook] ? hooks[currentHook].deps : undefined
   const hasUpdated = !oldDeps || !deps.every((dep, i)=> dep===oldDeps[i])
  
   if (hasUpdated) {
       if (hooks[currentHook] && hooks[currentHook].cleanUp) {
           hooks[currentHook].cleanUp()
       }
       
       const  cleanup = effect();
       hooks[currentHook] = {
           deps,
           cleanup
       }
   }
   
   currentHook++
}

之前的代码已经可以很好的演示了基本的底层实现原理,但是没有配合fiber。

那么配合fiber的版本,源码如下,

function useEffect(effect, deps) {
    // workInProgress, 当前处理的fiber 单元
    const oldHook = workInProgress.alternate && workInProgress.alternate.hooks && workInProgress.alternate.hooks[workInProgress.currentHook];
    
    const hasChanged = oldHook ? !deps || deps.some((dep,index) => dep!==oldHook.deps[index])
    
    const hook = {
        
        deps: deps,
        cleanup: oldHook ? oldHook.cleanup : null
    }
    
    workInProgress.hooks.push(hook)
    workInProgress.currentHook++
    
    if (hasChanged) {
        workInProgress.effects.push(hook)
    }
}
  1. 从当前的fiber 的alternate (即当前的fiber树获取对应的hooks信息, 这里既是复用)
  2. 如果存在 (fiber.alternate === true) , 则获取hooks数组对应当前currentHook索引的那个hook (currentHook, 已经在前面的篇章提及了非常多, 其目的就是维护组件内部所有hooks运行的顺序保持不变,即一个组件内部可以存储在多个hooks,比如 useState / useEffect / ...)
  3. 那么对比当前work in progress 的fiber 与 current fiber 的deps,如果完全相同则hasChanged 返回false。如果deps空,则hasChanged 就是true。 这里也解释了,如果没有任何deps, useEffect总会运行。
  4. 接着把当前的hook添加进workInProgress.hooks 数组
  5. 更新 currentHook 索引
  6. 如果hasChanged === true, 则hook需要被添加进 effects数组里面(这个effects数组在 fiber数据结构上面定义)

我先回答一下这里你可能会有的疑问,避免由于你没有阅读之前的几篇可能产生的疑惑?

这里的effects 数组以及 hooks数组的区别

  • hooks 数组

    • 用途:存储所有 Hook 的状态和逻辑(包括 useStateuseEffect)。
    • 作用:确保在每次渲染时,Hook 的调用顺序和状态一致。
  • effects 数组

    • 用途:专门用于收集需要在提交阶段执行的副作用(即 useEffect 的回调函数)。
    • 作用:确保副作用在组件渲染完成后统一执行,并且只在依赖项发生变化时执行。

effects 数组的生命周期

  • 收集阶段

    • 在渲染过程中,只有当 hasChangedtrue 时,hook 会被添加到 effects 数组中。
    • 这意味着,effects 数组只包含那些依赖项发生变化的副作用 Hook。
  • 提交阶段

    • 在提交阶段,commitWork 函数会遍历 effects 数组,执行每个副作用。

    • 执行完毕后,effects 数组会被清空:

      
      currentFiber.effects = [];
      
    • 这样,下一次渲染时,effects 数组将重新收集新的副作用,不会保留之前的未变化的副作用。

下一篇将从0-1开发useMemo 。

如果这样的长度/强度你觉得可以接受,觉得有帮助,可以继续阅读下一篇,实现一个 Mini React:核心功能详解 - useCallback的实现 。

如果文章对你有帮助,请点个赞支持一下!

啥也不是,散会。