xdm,又要到饭了,又更新代码了!
总结一下上一篇完成的内容,
- 完成了lazy / suspense的实现, 从底层理解运行机制。
有兴趣的可以点这里查看lazy / suspense的实现, 从底层理解运行机制
这一篇我们继续完成一个性能相关的api, memo。
memo 是优化 React 应用性能的有效手段之一。React.memo 是一个高阶组件(Higher-Order Component,HOC),用于在组件的 props 未发生变化时,跳过组件的重新渲染,从而提升性能。
理解 React.memo 的工作原理
React.memo 是一个高阶组件,用于优化函数组件。当传递给组件的 props 没有发生变化时,React.memo 会跳过组件的渲染过程,直接复用上一次渲染的结果。这对于避免不必要的渲染,提升应用性能尤其有效。
基本用法:
const MemoizedComponent = React.memo(Component, [areEqual]);
// 或者使用默认的浅比较
const MemoizedComponent = React.memo(Component);
-
Component:需要进行优化的函数组件。 -
areEqual(可选) :一个自定义的比较函数,用于判断props是否相等。返回true表示props相等,组件不需要重新渲染。
实现 memo 函数
实现步骤:
- 创建
memo函数:接收一个组件和一个可选的比较函数,返回一个新的组件。 - 在 Fiber 节点中标记
memo:在 Fiber 树中标记被memo包装的组件,以便在调和阶段进行优化。 - 在调和阶段跳过不必要的渲染:当检测到
props没有变化时,跳过该组件的渲染。
代码实现:
function memo(Component, areEqual) {
return function MemoizedComponent(props) {
return {
type: Component,
props,
$$typeof: Symbol.for('react.memo')
compare: areEqual || shallowCompare
}
}
}
function shallowCompare(prevProps, nextProps) {
const prevKeys = Object.keys(prevProps)
const nextKeys = Object.keys(nextProps)
if (prevKeys.length !== nextKeys.length) {
return false
}
for (let key of prevKeys) {
if (prevKeys[key] !== nextProps[key]) {
return false
}
}
return true
}
集成 memo 到 之前实现的 Fiber 和调和步骤,
要让 memo 正常工作,需要完成几个步骤,具体如下:
- 识别
memo组件:在 Fiber 树中,memo组件会有特殊的标记(如$$typeof: Symbol.for('react.memo'))。 - 比较
props:在调和过程中,当遇到memo组件时,使用其比较函数(自定义的或默认的浅比较)来判断props是否发生变化。 - 决定是否跳过渲染:如果
props没有变化,则复用旧 Fiber,跳过子树的渲染;否则,进行正常的渲染和更新。
修改之前篇章的 reconcileChildren 函数以支持 memo:
function reconcileChildren(fiber, children) {
const oldFiberMap = {};
let oldFiber = fiber.alternate ? fiber.alternate.child : null;
// 构建旧 Fiber 的 key 映射
while (oldFiber) {
const key = oldFiber.key || oldFiber.index;
oldFiberMap[key] = oldFiber;
oldFiber = oldFiber.sibling;
}
let prevSibling = null;
let newFiber = null;
for (let index = 0; index < children.length; index++) {
const child = children[index];
const key = child.key || index;
const matchedOldFiber = oldFiberMap[key];
if (matchedOldFiber) {
// 检查是否是 memo 组件
if (child.$$typeof === Symbol.for('react.memo')) {
const { type: Component, props, compare } = child;
// 获取旧的 props
const oldProps = matchedOldFiber.props;
const areEqual = compare(oldProps, props);
if (areEqual) {
// Props 没有变化,复用旧 Fiber,跳过渲染
newFiber = matchedOldFiber;
newFiber.alternate = matchedOldFiber;
newFiber.effectTag = null; // 清除旧的 effectTag
} else {
// Props 发生变化,创建新的 Fiber
newFiber = createWorkInProgress(matchedOldFiber, props);
newFiber.type = Component;
newFiber.key = key;
newFiber.effectTag = 'UPDATE';
}
} else {
// 普通组件,复用旧 Fiber 并标记为更新
const type = typeof child === 'string' || typeof child === 'number' ? TEXT_ELEMENT : child.type;
newFiber = createWorkInProgress(matchedOldFiber, typeof child === 'object' ? child.props : { nodeValue: child });
newFiber.type = type;
newFiber.key = key;
newFiber.effectTag = 'UPDATE';
}
delete oldFiberMap[key];
} else {
// 创建新的 Fiber
const type = typeof child === 'string' || typeof child === 'number' ? TEXT_ELEMENT : child.type;
newFiber = child.$$typeof === Symbol.for('react.memo')
? {
type: child.type.type, // 原始组件类型
props: child.props,
$$typeof: child.$$typeof,
compare: child.compare || shallowCompare,
key,
effectTag: 'PLACEMENT',
stateNode: createDom(child),
hooks: [],
currentHook: 0,
alternate: null,
contexts: new Map(),
parent: fiber,
child: null,
sibling: null,
effects: []
}
: new FiberNode(type, typeof child === 'object' ? child.props : { nodeValue: child }, fiber);
if (child.$$typeof === Symbol.for('react.memo')) {
newFiber.effectTag = 'PLACEMENT';
newFiber.stateNode = createDom(newFiber);
} else {
newFiber.effectTag = 'PLACEMENT';
newFiber.stateNode = createDom(newFiber); // 初始化 DOM 节点
}
}
if (index === 0) {
fiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
}
// 标记旧节点为删除
Object.values(oldFiberMap).forEach(fiberToDelete => {
fiberToDelete.effectTag = 'DELETION';
});
}
说明:
-
识别
memo组件:通过检查child.$$typeof === Symbol.for('react.memo')来识别是否是memo组件。 -
比较
props:如果是memo组件,使用其compare函数(自定义或默认的浅比较)来判断props是否变化。 -
复用或更新:
- 复用:如果
props没有变化,复用旧的 Fiber,并跳过子树的渲染 (标记fiber 的 effectTag null)。 - 更新:如果
props发生变化,创建新的 Fiber,并标记为'UPDATE',进行正常的渲染和更新。
- 复用:如果
这篇我们尝试实现了一个memo,以及讲解了它的底层实现原理。
下一篇,我们将从0-1完成 createPortal。
如果文章对你有帮助,请点个赞支持一下!
啥也不是,散会。