实现一个 Mini React:核心功能详解 - useCallback / useMemo的实现

175 阅读3分钟

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

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

  1. 配合fiber改进了useEffect的实现, 从底层理解运行机制。

有兴趣的可以点这里查看重构useEffect的实现

这一篇我们实现一个常用的api,useMemo

简单介绍

useMemo 是 React 中用于性能优化的 Hook。它允许你记忆(缓存)一个计算结果,只有当其依赖项发生变化时才重新计算。这样可以避免在每次渲染时都进行昂贵的计算,提升应用性能。

基本用法

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • computeExpensiveValue:一个耗时的计算函数。
  • 依赖项数组 [a, b] :只有当 ab 发生变化时,computeExpensiveValue 才会重新执行,memoizedValue 才会更更新。

关键点

  • 缓存机制useMemo 会记忆上一次的计算结果,并在依赖项未变化时返回缓存值。
  • 依赖项管理:正确管理依赖项数组,确保缓存的准确性。

实现原理

实现 useMemo 需要以下几个步骤:

  1. 存储缓存值和依赖项:在 hooks 数组中,为每个 useMemo Hook 存储其缓存值和依赖项。
  2. 依赖项比较:每次渲染时,比较当前依赖项与上一次的依赖项,判断是否需要重新计算。
  3. 返回缓存值:如果依赖项未变化,返回上一次的缓存值;否则,重新计算并更新缓存值。

接下来我们来实现一下(实现的代码会用到之前几篇完成的代码)。useMemo 关键的点就是缓存在哪里,从哪里提取缓存。其实如果你阅读了之前的几篇fiber架构的实现,对你来说就是非常自然就能理解的。其原理就是借助double buffing,即双fiber树。每次运行会优先对比依赖项,如果没有变化+不是首次渲染,则尝试直接从current fiber 树找出对应的fiber获取缓存而不是重新运算。

function useMemo(factory, deps) {
    const oldHook = workInProgress.alternate && workInProgress.alternate.hooks && workInProgress.alternate.hooks[workInProgress.currentHook]
    const hasChanged = oldHook ? !deps || deps.some((dep, index)=> dep !== oldHook.deps[index]) : true
    const hook =  {
        value: hasChanged ? factory() : oldHook.value,
        deps: deps
    }
    
    workInProgress.hooks.push(hook)
    workInProgress.currentHook++
    
    return hook.value
}

详细解析

  1. 获取旧 Hook

    • 从交替 Fiber (alternate) 中获取旧的 useMemo Hook, 从 current fiber 树。
  2. 判断依赖项是否变化

    • 如果依赖项数组 deps 中有任何一个项发生变化,hasChangedtrue,表示需要重新计算。
    • 如果没有提供 deps,默认认为依赖项已变化。
  3. 创建新 Hook

    • 如果 hasChangedtrue,执行 factory 函数并存储返回值。
    • 否则,使用上一次的缓存值。
  4. 添加到 Hooks 列表

    • 将新 Hook 对象添加到当前 Fiber 的 hooks 数组。
    • 递增 currentHook 索引,以确保 Hook 的调用顺序一致(这个已经前几篇反复提过了)。
  5. 返回缓存值

    • 返回 hook.value,即当前的缓存值。

useCallback 的实现

function useCallback(callback, deps) {
  return useMemo(() => callback, deps);
}

解释

  • useCallback 实际上是 useMemo 的一种特殊用法,返回一个记忆的回调函数。
  • 通过将 callback 包装在一个函数中传递给 useMemo,确保只有当依赖项变化时,callback 才会被重新创建。

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

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

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

啥也不是,散会。