React memo

43 阅读1分钟

基于 React 18.3.0 源码

React.memo 函数的主要操作

1. 参数接收

export function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {...}
  • type: 要被缓存的 React 组件
  • compare: 可选的自定义比较函数

2. 开发环境验证

if (__DEV__) {
  if (!isValidElementType(type)) {
    console.error(
      'memo: The first argument must be a component. Instead ' +
        'received: %s',
      type === null ? 'null' : typeof type,
    );
  }
}
  • 验证传入的组件是否为有效的 React 元素类型
  • 如果不是,会在控制台输出错误信息

3. 创建元素类型对象

const elementType = {
  $$typeof: REACT_MEMO_TYPE,
  type,
  compare: compare === undefined ? null : compare,
};

这是核心操作,创建一个包含三个关键属性的对象:

  • $$typeof: REACT_MEMO_TYPE: 标识这是一个 memo 组件
  • type: 原始的组件函数或类
  • compare: 比较函数(没有提供则为 null)

4. DisplayName 处理 (开发环境)

if (__DEV__) {
  let ownName;
  Object.defineProperty(elementType, 'displayName', {
    enumerable: false,
    configurable: true,
    get: function() {
      return ownName;
    },
    set: function(name) {
      ownName = name;
      // 如果内部组件是匿名函数,继承这个名称
      if (!type.name && !type.displayName) {
        type.displayName = name;
      }
    },
  });
}
  • 为调试工具设置 displayName 属性
  • 对于匿名函数组件,会自动继承 displayName

5. 返回结果

return elementType;

返回包装后的元素类型对象

总结

memo 函数本身不执行任何缓存逻辑,它只是:

  1. 创建一个标记对象,告诉 React 这是一个需要缓存的组件
  2. 保存原始组件比较函数的引用
  3. 添加开发时的调试信息

实际的缓存和比较逻辑是在 React 的协调(reconciliation)过程中处理的,当 React 遇到 $$typeof: REACT_MEMO_TYPE 的组件时,会执行相应的优化策略。

完整代码

// packages\react\src\ReactMemo.js
import {REACT_MEMO_TYPE} from 'shared/ReactSymbols';

import isValidElementType from 'shared/isValidElementType';

export function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  if (__DEV__) {
    if (!isValidElementType(type)) {
      console.error(
        'memo: The first argument must be a component. Instead ' +
          'received: %s',
        type === null ? 'null' : typeof type,
      );
    }
  }
  const elementType = {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
  if (__DEV__) {
    let ownName;
    Object.defineProperty(elementType, 'displayName', {
      enumerable: false,
      configurable: true,
      get: function() {
        return ownName;
      },
      set: function(name) {
        ownName = name;

        // The inner component shouldn't inherit this display name in most cases,
        // because the component may be used elsewhere.
        // But it's nice for anonymous functions to inherit the name,
        // so that our component-stack generation logic will display their frames.
        // An anonymous function generally suggests a pattern like:
        //   React.memo((props) => {...});
        // This kind of inner function is not used elsewhere so the side effect is okay.
        if (!type.name && !type.displayName) {
          type.displayName = name;
        }
      },
    });
  }
  return elementType;
}

// packages\shared\ReactSymbols.js
export const REACT_MEMO_TYPE = Symbol.for('react.memo');
// packages\shared\isValidElementType.js
export default function isValidElementType(type: mixed) {
  if (typeof type === 'string' || typeof type === 'function') {
    return true;
  }

  // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill).
  if (
    type === REACT_FRAGMENT_TYPE ||
    type === REACT_PROFILER_TYPE ||
    (enableDebugTracing && type === REACT_DEBUG_TRACING_MODE_TYPE) ||
    type === REACT_STRICT_MODE_TYPE ||
    type === REACT_SUSPENSE_TYPE ||
    type === REACT_SUSPENSE_LIST_TYPE ||
    (enableLegacyHidden && type === REACT_LEGACY_HIDDEN_TYPE) ||
    type === REACT_OFFSCREEN_TYPE ||
    (enableScopeAPI && type === REACT_SCOPE_TYPE) ||
    (enableCacheElement && type === REACT_CACHE_TYPE) ||
    (enableTransitionTracing && type === REACT_TRACING_MARKER_TYPE)
  ) {
    return true;
  }

  if (typeof type === 'object' && type !== null) {
    if (
      type.$$typeof === REACT_LAZY_TYPE ||
      type.$$typeof === REACT_MEMO_TYPE ||
      type.$$typeof === REACT_PROVIDER_TYPE ||
      type.$$typeof === REACT_CONTEXT_TYPE ||
      type.$$typeof === REACT_FORWARD_REF_TYPE ||
      // This needs to include all possible module reference object
      // types supported by any Flight configuration anywhere since
      // we don't know which Flight build this will end up being used
      // with.
      type.$$typeof === REACT_MODULE_REFERENCE ||
      type.getModuleId !== undefined
    ) {
      return true;
    }
  }

  return false;
}