核心实现概念
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
的优化是通过以下机制实现的:
-
提前中止渲染 :在
beginWork
阶段,如果检测到是 memo 组件且 props 未变化,React 会立即中止当前组件的渲染工作。 -
子树复用 :通过
bailoutOnAlreadyFinishedWork
函数,React 会标记整个子树为"无需更新",直接复用之前的渲染结果。 -
跳过副作用:由于组件没有重新渲染,相关的副作用(如 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 节点的
memoizedState
和memoizedProps
中) - 比较成本与渲染成本的权衡:浅比较的计算成本远低于组件的渲染成本(包括虚拟 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
的实现原理可以概括为:
- 高阶组件包装:通过创建特殊类型的 React 元素标记 memo 组件
- 协调阶段拦截:在协调过程中识别 memo 组件并执行特殊处理
- Props 比较:使用浅比较或自定义比较函数判断 props 是否变化
- 渲染优化:如果 props 未变化,跳过组件的渲染和子树的协调过程
- 结果复用:直接复用之前的渲染结果,避免不必要的计算和 DOM 操作
这种实现方式体现了 React 性能优化的核心思想:用较小的比较成本换取可能很大的渲染成本节约。