React 的 useMemo 是怎么实现的?你自己能造一个吗?

React useMemo实现原理深度解析:从源码到手动实现

当Vue开发者初次接触React的useMemo时,往往会下意识地将其等同于computed属性。但当我们深入源码时会发现:useMemo本质上是一个带有依赖追踪的计算结果缓存系统,它更像是经过精密设计的备忘录而非响应式魔法。本文将带您穿透表象,解密其核心实现机制,并亲手打造一个简易版useMemo。

一、React useMemo的三大核心实现原理

1.1 Hooks的链表存储结构

React通过单向链表结构存储Hooks状态,每个useMemo对应链表中的一个节点。在组件首次渲染时创建包含三个关键属性的节点:

  • memoizedState:缓存的计算结果
  • dependencies:依赖项数组
  • queue:更新队列(用于调度重计算)

1.2 依赖对比算法

React采用浅比较(shallow compare)进行依赖项比对,其核心逻辑可简化为:

function areDependenciesEqual(prevDeps, nextDeps) {
  for(let i=0; i

1.3 缓存更新策略

当检测到依赖变化时,React会触发同步重计算而非异步更新。源码中的核心逻辑伪代码如下:

function updateMemo(create, deps) {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  
  if (hook.memoizedState !== null) {
    const prevState = hook.memoizedState;
    if (areDependenciesEqual(prevState.dependencies, nextDeps)) {
      return prevState.memoizedValue;
    }
  }
  
  const nextValue = create();
  hook.memoizedState = { 
    memoizedValue: nextValue,
    dependencies: nextDeps 
  };
  return nextValue;
}

二、手动实现一个简易版useMemo

2.1 存储系统设计

我们使用闭包变量模拟React的Hooks存储机制:

let memoCache = [];
let cursor = 0;

function resetCursor() {
  cursor = 0;
}

2.2 核心逻辑实现

function myUseMemo(create, deps) {
  // 初始化缓存节点
  if (!memoCache[cursor]) {
    memoCache[cursor] = {
      value: create(),
      deps: deps
    };
    cursor++;
    return memoCache[cursor到1].value;
  }

  // 依赖比对
  const shouldRecompute = deps.some((dep, i) => 
    !Object.is(dep, memoCache[cursor].deps[i])
  );

  if (shouldRecompute) {
    memoCache[cursor] = {
      value: create(),
      deps: deps
    };
  }

  cursor++;
  return memoCache[cursor-1].value;
}

2.3 与React实现的关键差异

  • 存储结构差异:使用数组替代链表,简化状态管理
  • 渲染周期处理:缺少React的渲染队列调度机制
  • 依赖处理:未实现React的自动依赖收集功能

三、使用useMemo的五个黄金准则

3.1 必须使用的场景

  • 复杂计算缓存:如矩阵运算、大数据量转换
  •   // 计算斐波那契数列
      const fib = useMemo(() => {
        let a=0, b=1;
        for(let i=0; i<100000; i++) [a,b] = [b, a+b];
        return b;
      }, []);
      
  • 组件渲染优化:避免无效的子组件重渲染
  •   const memoizedChild = useMemo(() => , [data]);
      

3.2 必须警惕的陷阱

  • 过早优化反噬:简单计算使用useMemo反而增加内存开销
  • 依赖数组陷阱:忘记更新依赖项会导致缓存失效
  • 副作用滥用:在useMemo中执行API调用等副作用操作

3.3 性能评估策略

console.time('expensiveCalc');
// 需要缓存的复杂计算
console.timeEnd('expensiveCalc'); // 超过1ms的计算才值得缓存

四、从原理到实践的正确认知

通过源码分析我们发现,useMemo本质上是一个依赖驱动的缓存系统,而非响应式状态管理工具。其实现亮点在于:

  1. 与React渲染流程深度集成的更新机制
  2. 基于Object.is的精确依赖比较算法
  3. 链表结构带来的多Hook协同能力

当我们在日常开发中遇到以下场景时,才是useMemo真正该登场的时候:

  • 计算耗时超过1ms的复杂逻辑
  • 需要保持引用稳定的对象/数组
  • 高频交互组件的渲染优化

记住:性能优化不是银弹,useMemo更不是装饰品。 只有理解其实现原理,才能让这个工具真正为你的应用加速。