React 源码:性能优化—— PureComponent 和 memo
最近在学习慕课网 手写 React 高质量源码迈向高阶开发,之前自己也尝试看过源码,不过最终放弃了
放弃的最主要原因是 react 内部的调用链太长了,每天在缕清调用链上都花了不少时间,createRoot 都没有看完
最近看到慕课网有一个 react 源码课,就想着跟着课程然后在自己源码,看看这次能够看到什么地步
它这个课程前八章 是 react@16 的源码,从第九章开始才是 react@18 的源码
React 源码系列:
本篇是介绍 react 两个性能优化的组件,PureComponent 和 memo,它们都是用来优化性能的,那么它们是如何实现的呢?
PureComponent 是用于类组件性能优化的,在 props 和 state 没有变化时,就不重新渲染类组件
memo 是函数组件的 PureComponent,用 memo 包裹的函数组件,在 props 没有变化时,函数组件时不会重新渲染的,
由于函数组件没有 state,所以 memo 只能用来优化 props 没有变化的情况
PureComponent
PureComponent 是实现了 shouldComponentUpdate 的 Component 它是对 state 或 props 进行浅比较
什么是浅比较?
就是只比较第一层,如果第一层的值相同,就认为两个对象相同,不会再继续比较下去
那如何实现浅比较呢?
- 首先判断两个对象是否相同,如果相同,直接返回
true - 如果两个对象中有一个不是对象,那么直接返回
false- 可以用上文中的
getType函数来判断
- 可以用上文中的
- 获取两个对象的所有
key,如果key的数量不相同,直接返回false - 遍历其中一个对象,如果这个对象的
key在另一个对象中不存在,或者这两个对象这个key的所对应的value不一样,返回false - 直接返回
true
function shallowCompare(obj1, obj2) {
// 如果两个对象相同,直接返回 true
if (obj1 === obj2) return true;
// 如果两个对象中有一个不是对象,直接返回 false
if (getType(obj1) !== "object" || getType(obj2) !== "object") return false;
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
// 如果两个对象的 key 的数量不相同,直接返回 false
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
// 如果 obj2 中不存在 obj1 的 key,或者 obj1 和 obj2 的 key 对应的值不相同,直接返回 false
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) return false;
}
// 直接返回 true
return true;
}
PureComponent 的实现
class PureComponent extends Component {
shouldComponentUpdate(nextProps, nextState) {
return (
!shallowCompare(this.props, nextProps) ||
!shallowCompare(this.state, nextState)
);
}
}
memo
memo 是用于函数组件,如果 props 没有变化,就不会渲染这个函数组件
使用 memo 包裹后的函数组件返回是个什么呢?
我们将它打印出来
const MemoChild = memo(function MemoChild(props) {
return <div>{props.name}</div>;
});
console.log(MemoChild);
打印出来我们看到这是一个 $$typeof 为 Symbol("react.memo") 的对象,这不是虚拟 DOM,它是 memo 对象,它的 type 是一个函数组件
// 输出
{
$$typeof: Symbol("react.memo"), // memo 兑现
compare: null,
type: () => {}, // 函数组件
}
虚拟 DOM 是一个 JSX 对象,它的 $$typeof 是 Symbol("react.element")
console.log(<MemoChild />);
// 输出
{
$$typeof: Symbol("react.element"), // jsx
key: null,
props: {},
ref: null,
type: {
$$typeof: Symbol("react.memo"), // memo 对象
compare: null,
type: () => {}, // 函数组件
}
}
下面就来实现 memo 函数
首先在 react.js 中定义一个 memo 函数,返回一个 memo 对象,这个对象包括三个属性:
$$typeof:用来标识这是一个memo对象type:函数组件compare:决定是否渲染函数组件,如果返回false就渲染,返回true就不渲染- 正好和
shouldComponentUpdate函数相反
- 正好和
function memo(type, compare) {
return {
$$typeof: REACT_MEMO,
type,
compare,
};
}
我们在处理 memo 函数时,有两处需要处理:
- 初次渲染时,需要对
memo包裹的组件特殊处理 - 之后在每次更新时,如果是个函数组件,需要进行特殊处理
我们先来看初次渲染时,memo 组件如何处理
初次渲染
初次渲染实在 createDOM 函数中处理
这里还是要强调一遍,<MemoChild /> 这不是 memo 对象,这是 jsx 对象,它的 type 才是 memo 对象
function createDOM(VNode) {
let { type, props, $$typeof, ref } = VNode;
let dom;
// <MemoChild /> 这不是 memo 对象,这是 jsx 对象,这里会递归处理,第二次进来时,type 是 memo 对象
if (type && type.$$typeof === REACT_MEMO) {
return getDomByMemoFunctionComponent(VNode);
}
// ...
}
getDomByMemoFunctionComponent 函数比较简单,和处理函数组件 getDomByFunctionComponent 几乎一样,唯一不同的是,type 是 memo 对象,type.type 才是函数组件
function getDomByMemoFunctionComponent(VNode) {
let { type, props } = VNode;
// 和函数不一样的是:type 是 memo 对象,type.type 才是函数组件
let renderVNode = type.type(props);
if (!renderVNode) return null;
VNode.oldRenderVNode = renderVNode;
return createDOM(renderVNode);
}
更新
相比初次渲染,更新时处理有点复杂
首先在 deepDOMDiff 函数中需要增加对 memo 类型的处理条件
function deepDOMDiff(oldVNode, newVNode) {
let diffTypeMap = {
// ...
// memo 节点,type 是一个 memo 对象,需要拿到 $$typeof 属性,
MEMO: oldVNode.type.$$typeof === REACT_MEMO,
};
const DIFF_TYPE = Object.keys(diffTypeMap).filter(
(key) => diffTypeMap[key]
)[0];
switch (DIFF_TYPE) {
// ...
case "MEMO":
// 处理 memo 节点
updateMemoFunctionComponent(oldVNode, newVNode);
break;
default:
break;
}
}
具体的处理逻辑在 updateMemoFunctionComponent 函数中
具体分为这几步:
- 首先我们从
oldVNode中拿到type,这个type是memo对象 - 重新渲染有两种情况:
- 如果
type.compare不存在,我们则使用shallowCompare函数进行浅比较,shallowCompare返回false时,我们才进行重新渲染shallowCompare函数在PureComponent中实现了,它是对prevProps和nextProps进行浅比较
- 如果
type.compare且type.compare返回false时,我们才进行重现渲染
- 如果
- 重新渲染时,和函数组件一样,唯一的区别还是:
type不是函数组件,type.type才是函数组件
function updateMemoFunctionComponent(oldVNode, newVNode) {
let { type } = oldVNode;
// 如果 type.compare 不存在,我们则使用 shallowCompare 函数进行浅比较,shallowCompare 返回 false 时,我们才进行重新渲染
// 如果 type.compare 且 type.compare 返回 false 时,我们才进行重现渲染
if (
(!type.compare && !shallowCompare(oldVNode.props, newVNode.props)) ||
(type.compare && !type.compare(oldVNode.props, newVNode.props))
) {
const oldDOM = (newVNode.dom = findDOMByVNode(oldVNode));
if (!oldDOM) return;
const { type } = newVNode;
// 和函数不一样的是:type 是 memo 对象,type.type 才是函数组件
let renderVNode = type.type(newVNode.props);
updateDomTree(oldVNode.oldRenderVNode, renderVNode, oldDOM);
newVNode.oldRenderVNode = renderVNode;
} else {
// 不重新渲染
newVNode.oldRenderVNode = oldVNode.oldRenderVNode;
}
}
总结
PureComponent和memo都是用来优化性能的,它们都是对props进行浅比较,如果props没有变化,就不会重新渲染PureComponent是类组件使用,可以比较state和props,内部实现了shouldComponentUpdate生命周期函数,返回false不会重新渲染memo是函数组件使用,只能比较props,内部通过浅比较props来决定是否重新渲染- 它也接收一个
compare函数,如果compare函数返回true不会重新渲染
- 它也接收一个
源码
- shallowCompare
- PureComponent
memo:
转载自:https://juejin.cn/post/7307125383785513001