React 中 useMemo 和 useCallback 源码原理

直接展示 React 源码(如 react-reconciler 包中的具体实现)会非常复杂,因为它涉及到 Fiber 架构、调度器(Scheduler)、优先级(Lanes)、并发模式(Concurrent Mode)等众多交织的概念。为了清晰地理解 useMemouseCallback 的核心逻辑,我们将采用一种 高度简化但原理一致 的模拟实现方式来讲解。这种方式能帮助我们聚焦于这两个 Hook 本身的工作机制:依赖项比较值的缓存/复用

我们将模拟 React 内部处理 Hook 的基本流程,包括:

  1. Hook 状态存储 :每个 Hook 需要在组件的 Fiber 节点上存储其状态(比如 useMemo 存储的上一次计算结果和依赖项)。
  2. Dispatcher 机制:React 通过一个全局的 Dispatcher 对象来根据组件是首次渲染(mount)还是更新(update)来调用不同版本的 Hook 实现。
  3. 依赖项比较:核心逻辑,比较本次渲染传入的依赖项数组和上次存储的是否"相等"。
  4. 值/函数的创建与复用:根据依赖项比较结果决定是重新计算/创建,还是复用上一次的结果。

重要提示: 以下代码是 原理性模拟,并非 React 真实源码的拷贝。真实源码要复杂得多,但核心思想是一致的。我们的目标是通过这个模拟来理解其工作原理。

javascript 复制代码
// ============================================================
// 模拟 React 内部环境和基本结构
// ============================================================

// 全局变量,模拟当前正在渲染的 Fiber 节点和当前处理到的 Hook 索引
// 在真实的 React 中,这些信息是通过闭包和内部模块传递的
let currentlyRenderingFiber = null; // 当前正在处理的 Fiber 节点 (模拟)
let workInProgressHook = null;    // 当前正在处理的 Hook 节点 (模拟链表)
let hookIndex = 0;               // 当前 Hook 在链表中的索引

// 模拟 Fiber 节点结构 (极度简化)
// 真实 Fiber 包含大量状态、优先级、子/父/兄弟指针等信息
class FiberNode {
  constructor(tag, type) {
    this.tag = tag; // 'FunctionComponent', 'HostComponent', etc.
    this.type = type; // 对于函数组件,就是函数本身
    this.memoizedState = null; // 存储该 Fiber 对应的 Hooks 链表 或 Class Component 的 state
    // ... 其他很多属性
  }
}

// 模拟 Hook 节点结构 (极度简化)
// 真实的 Hook 节点是一个链表,存储 memoizedState, baseState, queue, next 等
class HookNode {
  constructor() {
    this.memoizedState = null; // 存储 Hook 的状态 (例如 useState 的 state, useMemo 的 [value, deps])
    this.next = null;          // 指向下一个 Hook 节点
  }
}

// 模拟 React 的调度和渲染入口 (极度简化)
function renderRoot(rootFiber) {
  console.log(`[Render Start] Fiber: ${rootFiber.type.name}`);
  currentlyRenderingFiber = rootFiber;
  hookIndex = 0; // 重置 Hook 索引
  workInProgressHook = currentlyRenderingFiber.memoizedState; // 获取上次渲染的 Hook 链表头

  // 调用函数组件,执行其内部的 Hooks
  const children = rootFiber.type({}); // 假设 props 是空对象

  currentlyRenderingFiber = null; // 渲染结束,重置
  workInProgressHook = null;
  hookIndex = 0;
  console.log(`[Render End] Fiber: ${rootFiber.type.name}`);
  return children;
}

// ============================================================
// 核心:模拟 Dispatcher 机制
// ============================================================

// React 内部根据组件是 Mount 还是 Update,会切换不同的 Dispatcher
// Dispatcher 包含所有 Hook 的具体实现

// Mount 阶段的 Hook 实现
const HooksDispatcherOnMount = {
  useMemo: mountMemo,
  useCallback: mountCallback,
  useState: mountState, // 假设还有 useState 等
  // ...其他 Hooks
};

// Update 阶段的 Hook 实现
const HooksDispatcherOnUpdate = {
  useMemo: updateMemo,
  useCallback: updateCallback,
  useState: updateState, // 假设还有 useState 等
  // ...其他 Hooks
};

// 全局变量,模拟 React 当前使用的 Dispatcher
// React.currentDispatcher.current 在渲染函数组件前会被设置
let ReactCurrentDispatcher = {
  current: null, // 指向 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate
};

// 辅助函数:获取当前正在处理的 Hook 节点
// 这个函数模拟了 React 如何在 Hook 调用时定位到对应的 Hook 存储单元
function resolveCurrentlyProcessingHook() {
  if (!currentlyRenderingFiber) {
    throw new Error("Hooks can only be called inside the body of a function component.");
  }

  // 尝试获取当前的 Hook 节点
  const currentHook = workInProgressHook;

  if (currentHook) {
    // 如果不是第一个 Hook,移动到下一个 Hook 节点
    workInProgressHook = currentHook.next;
  } else {
    // 如果是第一个 Hook 或者之前的 Hook 链表用完了 (Update 阶段发现新增 Hook 会报错)
    // 在 Mount 阶段,我们会创建新的 Hook 节点并添加到链表尾部
    if (currentlyRenderingFiber.memoizedState === null) {
      // Fiber 的第一个 Hook
      const newHook = new HookNode();
      currentlyRenderingFiber.memoizedState = newHook;
      workInProgressHook = newHook;
    } else {
      // Fiber 已经有 Hook 链表,找到链表尾部并添加新节点
      // (简化:真实 React 在 Update 时如果 Hook 数量变化会报错)
      // 这里我们简化处理,认为 Mount 阶段总能追加
      let lastHook = currentlyRenderingFiber.memoizedState;
      while (lastHook.next) {
        lastHook = lastHook.next;
      }
      const newHook = new HookNode();
      lastHook.next = newHook;
      workInProgressHook = newHook;
    }
    // 在 Update 阶段,如果 workInProgressHook 为 null,意味着 Hook 数量或顺序发生变化,
    // 真实 React 会抛出错误。这里简化,假设 Mount 阶段。
  }

  hookIndex++; // 移动索引
  return workInProgressHook; // 返回当前处理的 Hook 节点
}


// ============================================================
// 核心:依赖项比较函数
// ============================================================

/**
 * 比较新旧依赖项数组是否"浅相等"。
 * React 使用 Object.is 来进行比较。
 * @param {Array<any> | null | undefined} nextDeps 新的依赖项数组
 * @param {Array<any> | null | undefined} prevDeps 旧的依赖项数组
 * @returns {boolean} 如果依赖项相等则返回 true,否则返回 false
 */
function areDepsEqual(nextDeps, prevDeps) {
  // console.log('[areDepsEqual] Comparing:', nextDeps, 'vs', prevDeps);

  // 如果没有提供依赖项数组 (undefined),则每次都认为不相等,需要重新计算
  if (prevDeps === null || prevDeps === undefined || nextDeps === null || nextDeps === undefined) {
    // console.log('[areDepsEqual] Result: false (null/undefined deps)');
    return false;
  }

  // 如果依赖项数组长度不同,则一定不相等
  if (prevDeps.length !== nextDeps.length) {
    // console.log('[areDepsEqual] Result: false (length mismatch)');
    return false;
  }

  // 逐个比较依赖项,使用 Object.is
  // Object.is 与 === 的主要区别在于:
  // 1. Object.is(NaN, NaN) === true (=== 结果为 false)
  // 2. Object.is(+0, -0) === false (=== 结果为 true)
  // React 选择 Object.is 来处理这些边界情况
  for (let i = 0; i < prevDeps.length; i++) {
    if (!Object.is(nextDeps[i], prevDeps[i])) {
      // console.log(`[areDepsEqual] Result: false (mismatch at index ${i}: ${nextDeps[i]} vs ${prevDeps[i]})`);
      return false;
    }
  }

  // 所有依赖项都相等
  // console.log('[areDepsEqual] Result: true');
  return true;
}

// ============================================================
// `useMemo` 的模拟实现
// ============================================================

/**
 * Mount 阶段的 useMemo 实现。
 * @param {() => any} create 函数,用于计算 memoized 值
 * @param {Array<any> | null | undefined} deps 依赖项数组
 */
function mountMemo(create, deps) {
  // console.log(`[mountMemo] Hook Index: ${hookIndex}`);
  const hook = resolveCurrentlyProcessingHook(); // 获取当前 Hook 存储节点

  const nextDeps = deps === undefined ? null : deps; // 标准化 deps (undefined -> null)

  console.log(`[mountMemo] Creating value for the first time. Deps:`, nextDeps);
  const nextValue = create(); // 首次渲染,直接调用 create 函数计算值

  // 将计算出的值和依赖项存储在 Hook 节点上
  // 真实 React 中 memoizedState 结构更复杂,这里简化为 [value, deps]
  hook.memoizedState = [nextValue, nextDeps];
  // console.log(`[mountMemo] Stored state:`, hook.memoizedState);

  return nextValue; // 返回计算出的值
}

/**
 * Update 阶段的 useMemo 实现。
 * @param {() => any} create 函数,用于计算 memoized 值
 * @param {Array<any> | null | undefined} deps 依赖项数组
 */
function updateMemo(create, deps) {
  const hook = resolveCurrentlyProcessingHook(); // 获取当前 Hook 存储节点
  // console.log(`[updateMemo] Hook Index: ${hookIndex}. Previous state:`, hook.memoizedState);

  if (!hook || !hook.memoizedState) {
      // 这通常不应该发生,意味着 mount 时 state 没存好,或者 hook 链出错了
      console.error("[updateMemo] Error: Hook state not found during update. Re-calculating.");
      // 作为降级处理,重新计算(但不符合 React 行为)
      // 真实 React 在 Hook 顺序或数量不对时会报错
      return mountMemo(create, deps); // 借用 mount 逻辑(非标准行为)
  }

  const nextDeps = deps === undefined ? null : deps; // 标准化新的依赖项
  const [prevState, prevDeps] = hook.memoizedState; // 获取上次存储的值和依赖项

  // 核心逻辑:比较新旧依赖项
  if (areDepsEqual(nextDeps, prevDeps)) {
    // 如果依赖项没有变化,直接返回上次存储的值
    console.log(`[updateMemo] Dependencies haven't changed. Reusing previous value:`, prevState);
    return prevState;
  } else {
    // 如果依赖项变化了,重新调用 create 函数计算新值
    console.log(`[updateMemo] Dependencies changed. Recalculating value. Prev Deps:`, prevDeps, `Next Deps:`, nextDeps);
    const nextValue = create();

    // 更新 Hook 节点上存储的值和依赖项
    hook.memoizedState = [nextValue, nextDeps];
    console.log(`[updateMemo] Stored new state:`, hook.memoizedState);

    return nextValue; // 返回新计算的值
  }
}

// ============================================================
// `useCallback` 的模拟实现
// ============================================================

// useCallback(fn, deps) 本质上等价于 useMemo(() => fn, deps)

/**
 * Mount 阶段的 useCallback 实现。
 * @param {Function} callback 需要被 memoize 的回调函数
 * @param {Array<any> | null | undefined} deps 依赖项数组
 */
function mountCallback(callback, deps) {
  // console.log(`[mountCallback] Hook Index: ${hookIndex}`);
  const hook = resolveCurrentlyProcessingHook(); // 获取当前 Hook 存储节点

  const nextDeps = deps === undefined ? null : deps; // 标准化 deps

  console.log(`[mountCallback] Creating callback for the first time. Deps:`, nextDeps);
  const nextCallback = callback; // 首次渲染,直接使用传入的 callback

  // 将 callback 本身和依赖项存储起来
  // 注意:存储的是 callback 函数实例,不是它的返回值
  hook.memoizedState = [nextCallback, nextDeps];
  // console.log(`[mountCallback] Stored state: [Function, Deps]`);

  return nextCallback; // 返回该 callback
}

/**
 * Update 阶段的 useCallback 实现。
 * @param {Function} callback 需要被 memoize 的回调函数
 * @param {Array<any> | null | undefined} deps 依赖项数组
 */
function updateCallback(callback, deps) {
  const hook = resolveCurrentlyProcessingHook(); // 获取当前 Hook 存储节点
  // console.log(`[updateCallback] Hook Index: ${hookIndex}. Previous state saved.`);

  if (!hook || !hook.memoizedState) {
      console.error("[updateCallback] Error: Hook state not found during update. Re-creating callback.");
      return mountCallback(callback, deps); // 借用 mount 逻辑(非标准行为)
  }

  const nextDeps = deps === undefined ? null : deps; // 标准化新的依赖项
  const [prevCallback, prevDeps] = hook.memoizedState; // 获取上次存储的 callback 和依赖项

  // 核心逻辑:比较新旧依赖项
  if (areDepsEqual(nextDeps, prevDeps)) {
    // 如果依赖项没有变化,直接返回上次存储的 callback 函数实例
    console.log(`[updateCallback] Dependencies haven't changed. Reusing previous callback instance.`);
    return prevCallback;
  } else {
    // 如果依赖项变化了,使用本次渲染传入的新的 callback 函数实例
    console.log(`[updateCallback] Dependencies changed. Using new callback instance. Prev Deps:`, prevDeps, `Next Deps:`, nextDeps);
    const nextCallback = callback;

    // 更新 Hook 节点上存储的 callback 和依赖项
    hook.memoizedState = [nextCallback, nextDeps];
    console.log(`[updateCallback] Stored new state: [New Function, New Deps]`);

    return nextCallback; // 返回新的 callback 实例
  }
}

// ============================================================
// 模拟 React 对外暴露的 Hooks API
// ============================================================

// 这些是我们在组件中实际调用的 React.useMemo 和 React.useCallback
// 它们的作用是根据当前 Dispatcher 调用对应的实现 (mount 或 update)

const React = {
  useMemo: (create, deps) => {
    // console.log("React.useMemo called");
    const dispatcher = ReactCurrentDispatcher.current;
    if (!dispatcher) {
      throw new Error("Invalid hook call. Hooks can only be called inside the body of a function component.");
    }
    // 动态调用 dispatcher 上的 useMemo 方法
    return dispatcher.useMemo(create, deps);
  },

  useCallback: (callback, deps) => {
    // console.log("React.useCallback called");
    const dispatcher = ReactCurrentDispatcher.current;
    if (!dispatcher) {
      throw new Error("Invalid hook call. Hooks can only be called inside the body of a function component.");
    }
    // 动态调用 dispatcher 上的 useCallback 方法
    return dispatcher.useCallback(callback, deps);
  },

  // 模拟 useState 用于演示
  useState: (initialState) => {
    // console.log("React.useState called");
    const dispatcher = ReactCurrentDispatcher.current;
     if (!dispatcher) {
      throw new Error("Invalid hook call. Hooks can only be called inside the body of a function component.");
    }
    return dispatcher.useState(initialState);
  }
};

// 模拟 useState 的 mount 和 update 实现 (非常简化)
function mountState(initialState) {
    const hook = resolveCurrentlyProcessingHook();
    console.log(`[mountState] Hook Index: ${hookIndex}. Initializing state.`);
    let state;
    if (typeof initialState === 'function') {
        state = initialState();
    } else {
        state = initialState;
    }
    hook.memoizedState = state;
    const setState = (newState) => {
        // 简化:直接修改 state,并触发重新渲染(真实 React 有复杂的更新队列)
        console.log(`[setState] Updating state for Fiber ${currentlyRenderingFiber?.type.name}. New state:`, newState);
        // 假设这个 setState 会调度一次新的渲染
        // 在这个模拟中,我们手动调用 renderRoot 来模拟更新
    };
    return [hook.memoizedState, setState];
}

function updateState(initialState) {
    const hook = resolveCurrentlyProcessingHook();
    // console.log(`[updateState] Hook Index: ${hookIndex}. Reusing previous state:`, hook.memoizedState);
    const setState = (newState) => {
        console.log(`[setState] Updating state for Fiber ${currentlyRenderingFiber?.type.name}. New state:`, newState);
        // 简化处理:直接更新 hook 状态,真实情况更复杂
        hook.memoizedState = typeof newState === 'function' ? newState(hook.memoizedState) : newState;
        // 触发重渲染的逻辑省略...
    };
    return [hook.memoizedState, setState];
}


// ============================================================
// 示例:模拟一个函数组件的渲染过程
// ============================================================

let renderCount = 0;
let componentState = 0; // 外部模拟的状态,用于触发更新

// 模拟的函数组件
function MyComponent(props) {
  renderCount++;
  console.log(`\n--- Rendering MyComponent (render #${renderCount}, state: ${componentState}) ---`);

  // 假设有一个 state 触发更新
  // const [count, setCount] = React.useState(0); // 真实用法
  // 这里我们用外部变量 componentState 模拟

  // 示例 1: useMemo - 只有当 componentState 变化时才重新计算 expensiveValue
  const expensiveValue = React.useMemo(() => {
    console.log("  [useMemo] Executing expensive calculation...");
    let sum = 0;
    for (let i = 0; i < (componentState + 1) * 10; i++) { // 计算量依赖于 state
      sum += i;
    }
    console.log("  [useMemo] Calculation finished.");
    return sum;
  }, [componentState]); // 依赖项是 componentState
  console.log(`  Received expensiveValue: ${expensiveValue}`);


  // 示例 2: useMemo - 传入空数组 [], 只在 Mount 时计算一次
  const initialTimestamp = React.useMemo(() => {
      console.log("  [useMemo] Calculating initial timestamp (should run only once)...");
      return Date.now();
  }, []); // 空依赖数组
  console.log(`  Received initialTimestamp: ${initialTimestamp}`);


  // 示例 3: useCallback - 只有当 componentState 变化时才创建新的 handleLog 函数实例
  const handleLog = React.useCallback(() => {
    console.log(`  [Callback Executed] Current expensiveValue: ${expensiveValue}, State: ${componentState}`);
    // 这个回调函数本身很简单,但如果它被传递给子组件作为 prop,
    // useCallback 可以防止因为父组件渲染导致子组件不必要的重新渲染(如果子组件用了 React.memo)
  }, [componentState, expensiveValue]); // 依赖项 list 包含了 componentState 和 expensiveValue
  console.log(`  Received handleLog function instance.`);


  // 示例 4: useCallback - 传入空数组 [], 只在 Mount 时创建一次函数实例
  const handleMountLog = React.useCallback(() => {
      console.log("  [Callback Executed] This log function was created on mount.");
  }, []); // 空依赖数组
  console.log(`  Received handleMountLog function instance.`);

  // 模拟返回 JSX (这里只是返回一个描述)
  return `Component Render #${renderCount}`;
}


// --- 模拟渲染流程 ---

// 1. 首次渲染 (Mount)
console.log(">>> Simulating Initial Mount <<<");
const myFiber = new FiberNode('FunctionComponent', MyComponent);
// 设置为 Mount dispatcher
ReactCurrentDispatcher.current = HooksDispatcherOnMount;
renderRoot(myFiber);

// 打印首次渲染后的 Fiber 状态 (Hooks 链表)
console.log("\nFiber state after initial mount:", JSON.stringify(myFiber.memoizedState, (key, value) => {
    if (typeof value === 'function') return '[Function]';
    return value;
}, 2));


// 2. 模拟一次状态更新,触发重新渲染 (Update)
console.log("\n>>> Simulating Update (state changes) <<<");
componentState = 1; // 改变状态
// 设置为 Update dispatcher
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
renderRoot(myFiber); // 再次渲染同一个 Fiber

// 打印更新后的 Fiber 状态
console.log("\nFiber state after update:", JSON.stringify(myFiber.memoizedState, (key, value) => {
    if (typeof value === 'function') return '[Function]';
    return value;
}, 2));

// 3. 模拟另一次状态不变的渲染 (Update)
// 例如,父组件重新渲染,但传递给 MyComponent 的 props 没有变,并且 MyComponent 自身状态也没变
// 但在这个简化模拟中,我们直接调用 renderRoot,相当于强制渲染
console.log("\n>>> Simulating Update (state remains the same) <<<");
// componentState 保持为 1
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
renderRoot(myFiber);

// 打印更新后的 Fiber 状态 (应该和上一次一样)
console.log("\nFiber state after second update:", JSON.stringify(myFiber.memoizedState, (key, value) => {
    if (typeof value === 'function') return '[Function]';
    return value;
}, 2));


// 4. 模拟执行 memoized 回调
console.log("\n>>> Simulating Callback Execution <<<");
// 获取最后一次渲染得到的 handleLog 函数实例
const lastHandleLog = myFiber.memoizedState // hook 0 (useMemo)
                      .next // hook 1 (useMemo [])
                      .next.memoizedState[0]; // hook 2 (useCallback) -> [callback, deps] -> callback
lastHandleLog();

const lastHandleMountLog = myFiber.memoizedState // hook 0 (useMemo)
                            .next // hook 1 (useMemo [])
                            .next // hook 2 (useCallback)
                            .next.memoizedState[0]; // hook 3 (useCallback []) -> [callback, deps] -> callback
lastHandleMountLog();


// --- 再次状态更新 ---
console.log("\n>>> Simulating Another Update (state changes again) <<<");
componentState = 2; // 再次改变状态
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
renderRoot(myFiber);

console.log("\nFiber state after third update:", JSON.stringify(myFiber.memoizedState, (key, value) => {
    if (typeof value === 'function') return '[Function]';
    return value;
}, 2));

代码讲解概要:

  1. 模拟环境:

    • currentlyRenderingFiber, workInProgressHook, hookIndex: 模拟 React 内部追踪当前渲染状态的变量。
    • FiberNode, HookNode: 极度简化的 Fiber 和 Hook 节点类,用来存储状态 (memoizedState) 和 Hook 链表结构 (next)。真实结构复杂得多。
    • renderRoot: 模拟 React 发起组件渲染的入口函数,负责设置全局状态、调用组件函数、清空状态。
  2. Dispatcher 机制:

    • HooksDispatcherOnMount, HooksDispatcherOnUpdate: 两个对象,分别包含了 Hooks 在首次渲染和更新时的具体实现函数。
    • ReactCurrentDispatcher: 一个包含 current 属性的对象,React 在渲染组件前会将其指向 Mount 或 Update Dispatcher。
    • resolveCurrentlyProcessingHook: 关键的辅助函数,模拟 React 如何根据 hookIndex 找到或创建当前 Hook 对应的存储节点 (HookNode)。它处理了 Hook 链表的创建(Mount 时)和遍历(Mount 和 Update 时)。真实实现更复杂,涉及错误处理(如 Hook 顺序改变)。
  3. areDepsEqual 依赖项比较:

    • 这是 useMemouseCallback 的核心判断逻辑。
    • 处理 depsnullundefined 的情况(视为永不相等)。
    • 比较数组长度。
    • 使用 Object.is() 逐个比较依赖项。注释解释了为何使用 Object.is 而不是 ===
    • 重要性: 正确理解依赖项比较是使用这两个 Hook 的关键,错误的依赖项(比如每次渲染都创建新数组/对象作为依赖)会导致 memoization 失效。
  4. useMemo 实现 (mountMemo, updateMemo) :

    • mountMemo:
      • 获取 Hook 节点。
      • 标准化 deps
      • 直接调用 create 函数计算值。
      • [value, deps] 存储在 hook.memoizedState 中。
      • 返回计算值。
    • updateMemo:
      • 获取 Hook 节点。
      • hook.memoizedState 取出上次存储的 [prevState, prevDeps]
      • 调用 areDepsEqual 比较 nextDepsprevDeps
      • 如果相等: 直接返回 prevState,不执行 create 函数。
      • 如果不相等: 调用 create 函数计算 nextValue,将 [nextValue, nextDeps] 存入 hook.memoizedState,返回 nextValue
    • 注释详细解释了每一步的操作和原因。
  5. useCallback 实现 (mountCallback, updateCallback) :

    • mountCallback:
      • 获取 Hook 节点。
      • 标准化 deps
      • 直接使用传入的 callback 函数实例。 (注意:不是调用它!)
      • [callback, deps] 存储在 hook.memoizedState
      • 返回 callback 实例。
    • updateCallback:
      • 获取 Hook 节点。
      • 取出上次存储的 [prevCallback, prevDeps]
      • 调用 areDepsEqual 比较 nextDepsprevDeps
      • 如果相等: 返回 prevCallback (上一次的函数实例)。
      • 如果不相等: 使用本次传入的 callback 实例作为 nextCallback,将 [nextCallback, nextDeps] 存入 hook.memoizedState,返回 nextCallback
    • 强调了 useCallback 是 memoize 函数实例本身,而 useMemo 是 memoize 函数的执行结果。
  6. 模拟 React API (React.useMemo, React.useCallback, React.useState) :

    • 这些是我们平时在代码中写的 React.useMemo 等。
    • 它们的核心作用就是读取 ReactCurrentDispatcher.current,然后调用其上对应的 Hook 方法(useMemouseCallback)。这使得同一段 React.useMemo(...) 代码能在 Mount 和 Update 时表现不同。
    • 添加了简化的 useState 及其 mountState/updateState 来让示例组件能工作。
  7. 示例组件与渲染模拟 :

    • MyComponent: 一个简单的函数组件,使用了 useMemouseCallback
      • expensiveValue: 使用 useMemo 缓存一个计算结果,依赖于模拟的 componentState
      • initialTimestamp: 使用 useMemo 和空依赖数组 [],只在首次渲染时计算一次。
      • handleLog: 使用 useCallback 缓存一个回调函数,依赖于 componentStateexpensiveValue
      • handleMountLog: 使用 useCallback 和空依赖数组 [],只在首次渲染时创建一次函数实例。
    • 模拟渲染流程:
      • 创建 FiberNode
      • 首次 Mount: 设置 ReactCurrentDispatcherHooksDispatcherOnMount,调用 renderRoot。观察控制台输出,useMemo 的计算函数和 useCallback 的创建逻辑都被执行。
      • 首次 Update (state 改变): 改变 componentState,设置 Dispatcher 为 HooksDispatcherOnUpdate,再次调用 renderRoot。观察输出:
        • 依赖于 componentStateuseMemo (expensiveValue) 重新计算。
        • 空依赖的 useMemo (initialTimestamp) 没有重新计算,返回了缓存值。
        • 依赖于 componentStateuseCallback (handleLog) 创建了新的函数实例。
        • 空依赖的 useCallback (handleMountLog) 复用了旧的函数实例。
      • 第二次 Update (state 不变): 保持 componentState 不变,再次渲染。观察输出:
        • 所有 useMemouseCallback 都因为依赖项未变而复用了上一次的结果/实例,它们的计算函数/创建逻辑不再执行。
      • 模拟回调执行: 获取最后一次渲染得到的 useCallback 实例并执行它们,验证其行为。
      • 第三次 Update (state 再次改变): 再次改变 componentState 并渲染,验证行为是否符合预期。
    • 打印 Fiber state: 在每次渲染后打印 myFiber.memoizedState 的内容(简化展示),可以看到 Hook 链表以及每个 Hook 存储的状态([value/callback, deps])。

核心原理总结:

  1. 状态存储: 每个 useMemouseCallback 调用在组件的 Fiber 节点内部都有一个对应的 Hook 数据结构(模拟中的 HookNode),用于存储上一次计算/创建的值(或函数实例)以及当时的依赖项数组。
  2. 渲染时机区分: React 通过 Dispatcher 机制,在组件首次渲染(Mount)和后续更新(Update)时执行不同的 Hook 逻辑。
  3. Mount 阶段: 直接计算值(useMemo)或使用当前函数实例(useCallback),并将结果和依赖项存入 Hook 状态。
  4. Update 阶段:
    • 取出上次存储的值/函数和依赖项。
    • 获取本次渲染传入的新的依赖项。
    • 关键: 使用 areDepsEqual(内部基于 Object.is)浅比较新旧依赖项数组。
    • 依赖项未变: 返回上次存储的值/函数实例,跳过计算/创建过程。这就是性能优化的来源。
    • 依赖项改变: 重新计算值(useMemo)或使用本次传入的函数实例(useCallback),并将新的结果和新的依赖项存入 Hook 状态,然后返回新结果。
  5. useCallback(fn, deps)useMemo(() => fn, deps) 的语法糖: 它俩的底层机制几乎完全一样,只是 useCallback 专门用于 memoize 函数实例,而 useMemo 用于 memoize 任何计算结果。
相关推荐
一口一个橘子6 分钟前
[ctfshow web入门] web80
前端·web安全·网络安全
漫谈网络7 分钟前
基于原生JavaScript前端和 Flask 后端的Todo 应用
前端·javascript·后端·python·flask
moyu8413 分钟前
异步编程的"语法糖":为什么 async/await 甜到犯规
前端·javascript
小小小小宇19 分钟前
端到端(E2E)测试学习笔记
前端
前端小趴菜0522 分钟前
grid网格布局
前端·css·html
小小小小宇23 分钟前
一文搞定jest单元测试
前端
徐_三岁27 分钟前
const ‘不可变’到底是值不变还是地址不变
前端·javascript·vue.js
小小小小宇1 小时前
前端一文搞懂webpack loader
前端
小小小小宇1 小时前
一文搞懂webpack插件
前端
小小小小宇1 小时前
一文搞定ESlint插件
前端