React.memo 实现原理解析

核心实现概念

React.memo 的本质是一个高阶组件(HOC),它通过比较前后 props 来决定是否跳过组件的重新渲染。

1. 基本结构

在 React 源码中,React.memo 的实现大致如下:

javascript 复制代码
function memo(type, compare) {
  // 创建一个特殊的元素类型
  const elementType = {
    $$typeof: REACT_MEMO_TYPE, // 特殊标识符,表示这是一个 memo 组件
    type, // 被包裹的原始组件
    compare: compare === undefined ? null : compare, // 自定义比较函数
  };
  
  return elementType;
}

2. 在协调(Reconciliation)过程中的处理

当 React 遇到 REACT_MEMO_TYPE 类型的元素时,会执行特殊处理:

javascript 复制代码
// 简化版的协调逻辑
function updateMemoComponent(
  currentFiber, // 当前 Fiber 节点
  workInProgressFiber, // 工作中的 Fiber 节点
  Component, // 被 memo 包裹的组件
  nextProps, // 新的 props
  renderLanes // 渲染优先级
) {
  // 获取之前的 props
  const current = currentFiber.memoizedProps;
  
  // 决定使用哪种比较函数
  let compare = Component.compare;
  if (compare === null) {
    // 默认使用浅比较
    compare = shallowCompare;
  }
  
  // 检查 props 是否相等
  if (compare(current, nextProps)) {
    // Props 没有变化,完全跳过渲染
    // 复用之前的子节点
    return bailoutOnAlreadyFinishedWork(
      currentFiber,
      workInProgressFiber,
      renderLanes
    );
  }
  
  // Props 发生了变化,继续正常渲染流程
  return updateFunctionComponent(
    currentFiber,
    workInProgressFiber,
    Component,
    nextProps,
    renderLanes
  );
}

深入实现细节

1. 浅比较 (shallowCompare) 的实现

React 使用的默认浅比较函数类似于:

javascript 复制代码
function shallowCompare(prevProps, nextProps) {
  // 如果 props 对象引用相同,直接返回 true
  if (prevProps === nextProps) {
    return true;
  }
  
  // 检查 props 键的数量
  const prevKeys = Object.keys(prevProps);
  const nextKeys = Object.keys(nextProps);
  
  if (prevKeys.length !== nextKeys.length) {
    return false;
  }
  
  // 逐个比较每个属性值
  for (let i = 0; i < prevKeys.length; i++) {
    const key = prevKeys[i];
    
    // 使用 Object.is 进行严格比较(类似于 ===,但处理了 NaN 和 +0/-0 的情况)
    if (!Object.is(prevProps[key], nextProps[key])) {
      return false;
    }
  }
  
  return true;
}

2. Fiber 架构中的优化机制

在 React 的 Fiber 架构中,React.memo 的优化是通过以下机制实现的:

  1. 提前中止渲染 :在 beginWork 阶段,如果检测到是 memo 组件且 props 未变化,React 会立即中止当前组件的渲染工作。

  2. 子树复用 :通过 bailoutOnAlreadyFinishedWork 函数,React 会标记整个子树为"无需更新",直接复用之前的渲染结果。

  3. 跳过副作用:由于组件没有重新渲染,相关的副作用(如 useEffect)也不会被触发。

3. 与 React 渲染流程的整合

javascript 复制代码
// 简化的渲染流程
function beginWork(currentFiber, workInProgressFiber, renderLanes) {
  // 检查是否是 memo 组件
  if (workInProgressFiber.type && workInProgressFiber.type.$$typeof === REACT_MEMO_TYPE) {
    return updateMemoComponent(
      currentFiber,
      workInProgressFiber,
      workInProgressFiber.type.type, // 提取原始组件
      workInProgressFiber.pendingProps,
      renderLanes
    );
  }
  
  // 处理其他类型的组件...
}

性能考虑与实现优化

1. 记忆化策略

React.memo 的实现采用了记忆化(Memoization)策略:

  • 存储之前的结果 :React 会存储组件上一次的渲染结果(在 Fiber 节点的 memoizedStatememoizedProps 中)
  • 比较成本与渲染成本的权衡:浅比较的计算成本远低于组件的渲染成本(包括虚拟 DOM 创建和差异比较)

2. 选择性优化

React 不会对所有组件都应用 memo 优化,因为:

  • 比较本身有成本
  • 对于频繁更新或 props 经常变化的组件,memo 可能带来负收益

3. 与其他优化机制的协同

React.memo 与 React 的其他优化机制协同工作:

  • Context 优化 :即使使用 React.memo,如果组件消费的 Context 值发生变化,组件仍会重新渲染
  • 状态更新优化 :组件内部的状态更新不受 React.memo 影响

实际应用中的实现考虑

1. 自定义比较函数的高级用法

javascript 复制代码
// 深度比较实现(不推荐在生产环境使用,仅作示例)
function deepCompare(prevProps, nextProps) {
  return JSON.stringify(prevProps) === JSON.stringify(nextProps);
}

// 选择性比较
function selectiveCompare(prevProps, nextProps) {
  // 只比较我们关心的属性
  return prevProps.importantValue === nextProps.importantValue;
}

const ExpensiveComponent = React.memo(
  function ExpensiveComponent(props) {
    // 组件实现
  },
  selectiveCompare // 使用自定义比较函数
);

2. 与 Hooks 的交互

React.memo 的实现需要考虑与 Hooks 的交互:

javascript 复制代码
function MyComponent(props) {
  // 即使使用 React.memo,内部状态变化仍会导致重新渲染
  const [state, setState] = useState(0);
  
  // 使用 useMemo 和 useCallback 可以进一步优化
  const computedValue = useMemo(() => {
    return expensiveCalculation(props.someValue);
  }, [props.someValue]);
  
  const handleClick = useCallback(() => {
    // 处理点击
  }, []);
  
  return <div>{/* ... */}</div>;
}

export default React.memo(MyComponent);

总结

React.memo 的实现原理可以概括为:

  1. 高阶组件包装:通过创建特殊类型的 React 元素标记 memo 组件
  2. 协调阶段拦截:在协调过程中识别 memo 组件并执行特殊处理
  3. Props 比较:使用浅比较或自定义比较函数判断 props 是否变化
  4. 渲染优化:如果 props 未变化,跳过组件的渲染和子树的协调过程
  5. 结果复用:直接复用之前的渲染结果,避免不必要的计算和 DOM 操作

这种实现方式体现了 React 性能优化的核心思想:用较小的比较成本换取可能很大的渲染成本节约

相关推荐
前端拿破轮11 小时前
从零到一开发一个Chrome插件(三)
前端·chrome·浏览器
94very11 小时前
iframe实践
前端
用户857594145002911 小时前
产品让你写段炫彩炫酷的字体效果,你该怎么做?回答我?
前端
南北是北北11 小时前
Flow 热流
前端·面试
一只小风华~11 小时前
快速搭建一个Vue+TS+Vite项目
前端·javascript·vue.js·typescript·前端框架
m0_7381207211 小时前
CTFshow系列——命令执行web73-77(完结篇)
前端·安全·web安全·网络安全·ctfshow
呵阿咯咯11 小时前
前端开发典型问题解决方案:打包冲突、状态更新与性能优化
前端
前端搬运侠11 小时前
🚀 浏览器原理+网络知识面试必刷!50道高频面试题详解
前端
励扬程序11 小时前
Cloudflare workers 构建和部署无服务器功能、站点和全栈应用程序。
前端·全栈