最近在学习慕课网 手写 React 高质量源码迈向高阶开发,之前自己也尝试看过源码,不过最终放弃了
放弃的最主要原因是 react
内部的调用链太长了,每天在缕清调用链上都花了不少时间,createRoot
都没有看完
最近看到慕课网有一个 react
源码课,就想着跟着课程然后在自己源码,看看这次能够看到什么地步
它这个课程前八章 是 react@16
的源码,从第九章开始才是 react@18
的源码
React
源码系列:
- 第 1 篇:createElement 和 render 函数实现原理
- 第 2 篇:函数组件和类组件及 ref 和 setState 的实现
- 第 3 篇:优化渲染过程之 dom diff
- 第 4 篇:类组件增强------生命周期函数
- 第 5 篇:性能优化------
PureComponent
和memo
本篇是介绍 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
js
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
的实现
js
class PureComponent extends Component {
shouldComponentUpdate(nextProps, nextState) {
return (
!shallowCompare(this.props, nextProps) ||
!shallowCompare(this.state, nextState)
);
}
}
memo
memo
是用于函数组件,如果 props
没有变化,就不会渲染这个函数组件
使用 memo
包裹后的函数组件返回是个什么呢?
我们将它打印出来
js
const MemoChild = memo(function MemoChild(props) {
return <div>{props.name}</div>;
});
console.log(MemoChild);
打印出来我们看到这是一个 $$typeof
为 Symbol("react.memo")
的对象,这不是虚拟 DOM
,它是 memo
对象,它的 type
是一个函数组件
js
// 输出
{
$$typeof: Symbol("react.memo"), // memo 兑现
compare: null,
type: () => {}, // 函数组件
}
虚拟 DOM
是一个 JSX
对象,它的 $$typeof
是 Symbol("react.element")
js
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
函数相反
- 正好和
js
function memo(type, compare) {
return {
$$typeof: REACT_MEMO,
type,
compare,
};
}
我们在处理 memo
函数时,有两处需要处理:
- 初次渲染时,需要对
memo
包裹的组件特殊处理 - 之后在每次更新时,如果是个函数组件,需要进行特殊处理
我们先来看初次渲染时,memo
组件如何处理
初次渲染
初次渲染实在 createDOM
函数中处理
这里还是要强调一遍,<MemoChild />
这不是 memo
对象,这是 jsx
对象,它的 type
才是 memo
对象
js
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
才是函数组件
js
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
类型的处理条件
js
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
才是函数组件
js
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
: