基于 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 函数本身不执行任何缓存逻辑,它只是:
- 创建一个标记对象,告诉 React 这是一个需要缓存的组件
- 保存原始组件和比较函数的引用
- 添加开发时的调试信息
实际的缓存和比较逻辑是在 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;
}