在上一章节,我们讲解了普通函数组件更新的基本流程,也知道了一个组件修改状态触发的更新,它的子组件也跟着重新渲染的原因。
所以本节就将从React.memo
方法来解析函数组件的性能优化处理。
1,案例准备
这是之前的测试案例:
js
// MyFun.js
import { useState } from 'react'
import Child from './Child'
export default function MyFun(props) {
console.log('MyFun组件运行了')
const [count, setCount] = useState(1)
function handleClick() {
setCount(count + 1)
}
return (
<div className='MyFun'>
<div>state: {count}</div>
<Child name='Child'></Child>
<button onClick={handleClick}>更新</button>
</div>
)
}
js
// Child.js
export default function Child(props) {
console.log('Child组件运行了')
return (
<div className='Child'>
<div>Child子组件</div>
<div>name: {props.name}</div>
</div>
)
}
每当我们点击MyFun
组件中的更新按钮时,Child
子组件都会重新渲染一次:
我们点击了三次更新按钮,Child
子组件也跟着父组件重新渲染了三次,这就是普通的函数组件更新。
React.memo方法
React.memo
方法的作用:创建函数组件中的纯组件,在 props
没有发生变化的情况下跳过重新渲染re-render
。
此方法常用于普通函数组件或者
forwardRef
组件。
下面我们使用React.memo
方法来优化child
组件。
js
// MyFun.js
import { useState } from 'react'
import NewChild from './NewChild'
export default function MyFun(props) {
console.log('MyFun组件运行了')
const [count, setCount] = useState(1)
function handleClick() {
setCount(count + 1)
}
return (
<div className='MyFun'>
<div>state: {count}</div>
<NewChild name='NewChild'></NewChild>
<button onClick={handleClick}>更新</button>
</div>
)
}
js
// Child.js
import React from 'react'
function Child(props) {
console.log('Child组件运行了')
return (
<div className='Child'>
<div>Child子组件</div>
<div>name: {props.name}</div>
</div>
)
}
const NewChild = React.memo(Child)
export default NewChild
这时我们再点击MyFun
组件中的更新按钮时,Child
子组件就不会再跟着重新渲染了:
方法原理
首先查看memo
方法源码:
js
// react/src/ReactMemo.js
export function memo<Props>(
type: React$ElementType,
compare?: (oldProps: Props, newProps: Props) => boolean,
) {
// 定义一个新的组件,包装传入的函数组件
const elementType = {
// 存储组件类型 memo
$$typeof: REACT_MEMO_TYPE,
type,
# 一个cb回调函数,用于自定义校验props,根据校验结果决定是否需要重新渲染
compare: compare === undefined ? null : compare,
};
// 返回包装后的组件
return elementType;
}
从上面的源码可以看出,memo
方法的内容比较简单,而且和forwardRef
方法原理基本一致。它的方法内容就是定义一个新的内部组件,包装传入的函数组件,即将我们传递的函数组件存储到type
属性上,然后返回包装后的组件即memo
组件。
下面我们就开始解析React.memo
方法创建的memo
组件在加载和更新时具体的执行逻辑。
2,加载流程
首先查看memo
组件的加载流程,因为memo
组件首次加载会做一些比较重要的处理,我们需要了解。
开始执行memo
组件对应Fiber
节点的beginWork
工作:
js
// react-reconciler/src/ReactFiberBeginWork.new.js
function beginWork() {
...
// 根据fiber.tag,进入不同组件处理逻辑
switch (workInProgress.tag) {
case FunctionComponent: {}
case ClassComponent: {}
case HostRoot: {}
case HostComponent: {}
...
// 普通memo组件处理 14
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
// 进入加载流程
return updateMemoComponent()
}
// simpleMemo组件处理 15
case SimpleMemoComponent: {
return updateSimpleMemoComponent()
}
}
}
首次加载时,该Fiber
节点的tag
属性值为14
,直接进入MemoComponent
分支处理逻辑:
js
case MemoComponent: {
// 组件type原型
const type = workInProgress.type;
// 等待处理的props
const unresolvedProps = workInProgress.pendingProps;
// 处理props默认值,两次
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type, // memo组件对象
resolvedProps,
renderLanes,
);
}
这里取出Fiber
节点的type
属性内容,也就是memo
组件对象的内容:
然后就是设置props
的默认值:
js
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
这里做了两次默认值处理:
- 第一次是对
memo
组件的处理。 - 第二次是对包裹的
Child
组件默认值处理。
最后调用一个updateMemoComponent
方法,开始memo
组件的加载处理。
updateMemoComponent
查看updateMemoComponent
方法:
js
// react-reconciler/src/ReactFiberBeginWork.new.js
function updateMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any, // Fiber.type
nextProps: any,
renderLanes: Lanes,
): null | Fiber {
# 加载阶段
if (current === null) {
const type = Component.type;
if (
isSimpleFunctionComponent(type) &&
Component.compare === null &&
Component.defaultProps === undefined
) {
let resolvedType = type;
// 1,SimpleMemo组件加载
// 【重点】设置为SimpleMemo组件的tag,更新时就会走updateSimpleMemoComponent分支
workInProgress.tag = SimpleMemoComponent;
workInProgress.type = resolvedType;
return updateSimpleMemoComponent(
current,
workInProgress,
resolvedType,
nextProps,
renderLanes,
);
}
// 2,普通memo组件加载,直接创建子节点
const child = createFiberFromTypeAndProps(
Component.type,
null,
nextProps,
workInProgress,
workInProgress.mode,
renderLanes,
);
child.ref = workInProgress.ref;
child.return = workInProgress;
workInProgress.child = child;
return child;
}
# 更新阶段
const currentChild = ((current.child: any): Fiber); // This is always exactly one child
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (!hasScheduledUpdateOrContext) {
// This will be the props with resolved defaultProps,
// unlike current.memoizedProps which will be the unresolved ones.
const prevProps = currentChild.memoizedProps;
// Default to shallow comparison
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
// 跳过更新,返回原来的节点
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// React DevTools reads this flag.
// 创建新的节点
workInProgress.flags |= PerformedWork;
const newChild = createWorkInProgress(currentChild, nextProps);
newChild.ref = workInProgress.ref;
newChild.return = workInProgress;
workInProgress.child = newChild;
return newChild;
}
首先通过判断current
节点是否为null
来区分加载阶段和更新阶段。当前为加载阶段current
为null
,继续查看memo
组件加载阶段的逻辑处理。
js
const type = Component.type;
注意: 这里的Component
是memo
组件对象,可以查看上面的调试截图,取出的type
才是Child
组件本身。 在取出Child
组件函数后,又通过一个条件判断,将memo
组件的加载分成了两个分支逻辑:
simpleMemo
组件加载。- 普通
memo
组件加载。
js
if (isSimpleFunctionComponent(type) && Component.compare === null && Component.defaultProps === undefined) {
// 1,simpleMemo组件加载
}
// 2,普通memo组件加载
同时满足上面三个判断条件的就是simpleMemo
组件。
首先看第一个判断条件isSimpleFunctionComponent(type)
:是否为简单函数组件。
js
// 判断是否为普通函数组件 forwardRef组件是对象:{$$typeof: Symbol(react.forward_ref)}
export function isSimpleFunctionComponent(type: any) {
return (
typeof type === 'function' && // 排除forwardRef组件
!shouldConstruct(type) && // 排除类组件
type.defaultProps === undefined // 无defaultProps
);
}
注意: 这个方法的作用其实就是判断我们优化的这个组件:是否为简单函数组件:
- 第一个条件是排除
forwardRef
组件【对象】,因为memo
组件也可以接收forwardRef
组件。 - 第二个条件是排除类组件,不能存在构造器。
- 第三个条件是不存在
defaultProps
。
只有同时满足这三个条件,才表示是简单函数组件simpleFunctionComponent
。
再看simpleMemo
组件剩下的判断条件:即memo
组件没有compare
比较函数且自身也不存在defaultProps
。同时满足这三个条件即可以认定为simpleMemo
组件。
组件区分
到这里我们来进行一个总结,以此区分simpleMemo
组件和普通memo
组件:
simpleMemo
组件:
React.memo(Com, null)
方法组件参数为简单函数组件,且memo
组件自身无compare
参数,无defaultProps
。
memo
组件:不满足simpleMemo
组件条件的就是普通memo
组件,比如:
React.memo(Com, compare)
方法组件参数为简单函数组件,但memo
组件自身存在compare
参数或者defaultProps
。React.memo(Com, compare)
方法组件参数为forwardRef
组件,此时不管存不存在compare
参数或者defaultProps
,它都是普通memo
组件。
memo组件加载
如果是普通memo
组件:就是继续创建子节点内容,最后返回子节点。
js
// 普通memo组件加载
const child = createFiberFromTypeAndProps(
Component.type,
null,
nextProps,
workInProgress,
workInProgress.mode,
renderLanes,
);
child.ref = workInProgress.ref;
child.return = workInProgress;
workInProgress.child = child;
// Child组件 Fiber节点
return child;
通过普通memo
组件的加载可以得知一个结论:普通memo
组件会为包装的组件创建独立的的Fiber
节点。
simpleMemo组件加载
这里我们的重点是查看simpleMemo
组件的处理,因为当前我们传入的child
满足simpleMemo
组件的所有条件:
js
if (isSimpleFunctionComponent(type) && Component.compare === null && Component.defaultProps === undefined) {
# simpleMemo组件加载
let resolvedType = type;
// 【重点】设置为简单memo组件的tag,更新时就会走updateSimpleMemoComponent分支
workInProgress.tag = SimpleMemoComponent;
workInProgress.type = resolvedType;
// 简单函数组件,没有defaultProps
return updateSimpleMemoComponent(
current,
workInProgress,
resolvedType,
nextProps,
renderLanes,
);
}
注意: 在判定为simpleMemo
组件之后,将当前Fiber
节点的tag
属性设置为了SimpleMemoComponent
对应的值,作用是此节点在更新阶段就可以直接进入updateSimpleMemoComponent
分支逻辑处理,并且这里重置了Fiber
节点的type
属性,原来是memo
组件对象,现在变成了Child
组件函数【这样simpleMemo
组件在加载阶段就和普通函数组件一致了】,查看调试截图印证:
最后调用updateSimpleMemoComponent
方法,开始simpleMemo
组件的加载。
updateSimpleMemoComponent
查看updateSimpleMemoComponent
方法:
js
// react-reconciler/src/ReactFiberBeginWork.new.js
function updateSimpleMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
): null | Fiber {
# 更新阶段
if (current !== null) {
const prevProps = current.memoizedProps;
// 普通函数组件基本满足,props无变化,ref为null
if (
shallowEqual(prevProps, nextProps) &&
current.ref === workInProgress.ref &&
// Prevent bailout if the implementation changed due to hot reload.
(__DEV__ ? workInProgress.type === current.type : true)
) {
didReceiveUpdate = false;
workInProgress.pendingProps = nextProps = prevProps;
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
workInProgress.lanes = current.lanes;
// 复用原来的节点,跳过更新
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
} else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
}
}
}
# 加载阶段:其实就是普通函数组件加载逻辑
// 更新阶段:不满足上面的优化条件时,也会进入普通函数组件的更新流程
return updateFunctionComponent(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}
此方法中同样是通过判断current
节点是否有值来区分加载阶段和更新,当前为加载阶段最后直接调用了一个updateFunctionComponent
方法,也就是说simpleMemo
组件的加载逻辑实际上调用的普通函数组件的加载逻辑。
3,更新流程
触发更新
点击MyFun
组件的更新按钮,触发一次更新流程。
下面我们直接快进来到MyFun
组件的更新逻辑:
MyFun
组件在调用renderWithHooks
方法之后,会根据组件返回的jsx
内容重新创建所有子节点对应的react
元素对象。
其中就包含了memo
组件对应的react
元素对象:
这里react元素对象的type
属性存储的就是上面memo
方法里面的elementType
对象,而它内部的type
属性才是原始的child
组件。
下面我们继续快进,直接来到memo
组件的更新流程,开始执行memo
组件对应Fiber
节点的beginWork
工作:
js
// react-reconciler/src/ReactFiberBeginWork.new.js
function beginWork() {
if (current !== null) {
// 更新逻辑
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// 主要判断:props变化和context上下文变化
if (oldProps !== newProps || hasLegacyContextChanged() ) {
// props有变化或者上下文有变化时,Fiber节点自身需要更新就会进入这里
didReceiveUpdate = true;
} else {
// 无变化,进入Bailout策略
...
}
} else {
// 加载逻辑
didReceiveUpdate = false;
}
...
// 根据fiber.tag,进入不同组件处理逻辑
switch (workInProgress.tag) {
case FunctionComponent: {}
case ClassComponent: {}
case HostRoot: {}
case HostComponent: {}
...
// 普通memo组件处理 14
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
// 进入加载流程
return updateMemoComponent()
}
// simpleMemo组件处理 15
case SimpleMemoComponent: {
return updateSimpleMemoComponent()
}
}
}
这里memo
组件节点的pendingProps
属性接收的新建的react元素对象传递的props
数据,所以这里的新旧props
不相等,不满足Bailout
优化策略,会执行正常的更新流程。
simpleMemo组件更新
当前我们为simpleMemo
组件,它的tag
为15
【前面加载阶段中更新的tag
属性】,所以这里更新时会直接进入SimpleMemoComponent
分支逻辑,下面我们继续查看它的更新逻辑。
直接进入updateSimpleMemoComponent
方法的更新逻辑:
js
// react-reconciler/src/ReactFiberBeginWork.new.js
function updateSimpleMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
): null | Fiber {
# 更新阶段
if (current !== null) {
const prevProps = current.memoizedProps;
// 普通函数组件基本满足,props无变化,ref为null
if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref) {
didReceiveUpdate = false;
workInProgress.pendingProps = nextProps = prevProps;
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
workInProgress.lanes = current.lanes;
// 复用原来的节点,跳过更新
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
}
}
}
// 加载阶段
// 或者不满足优化策略条件时,进入普通函数组件更新流程
return updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes)
}
注意: 这里判断current
时和下面的updateFunctionComponent
并不是if else
,即不是二选一的逻辑。在当前更新流程中,如果不满足Bailout
优化策略条件,则还是会进入下面的updateFunctionComponent
方法,执行普通函数组件的更新流程。
所以
memo
方法优化函数组件的原理就是:在函数组件更新之前,再执行一次Bailout
策略校验。
我们继续查看simpleMemo
组件的优化逻辑,首先从current
节点上取出旧的props
对象:
js
const prevProps = current.memoizedProps;
然后调用默认的shallowEqual
方法对新旧props
对象进行浅比较,判断props
是否发生了变化:
js
if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref)
这里我们接着查看shallowEqual
浅比较方法:
shallowEqual
js
// packages\shared\shallowEqual.js
import is from './objectIs';
import hasOwnProperty from './hasOwnProperty';
// 浅比较
export default function shallowEqual(objA: mixed, objB: mixed): boolean {
// objA:旧的props; objB:新的props
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}
return true;
}
一进入shallowEqual
方法,首先就使用Object.is()
来判断新旧props
对象是否相等:
注意: 实际情况中组件更新时,新旧props
对象都不会相等,以当前案例中的MyFun
组件为例,每次当该组件更新时,它都会重新创建组件内所有的子节点的react
元素对象【其中就包括newChild
组件】,在创建react元素对象时,每次都是新建的一个props
对象,所以即使你的props
没有任何数据为空对象,它也不会和旧的props
对象相等。
yaml
{} !== {}
而Child
组件Fiber
节点的props
对象就是取自于它对应的react元素对象,所以这里第一个判断条件无法满足。
shallowEqual
方法剩下判断逻辑就比较简单了:通过Object.keys
方法得到新旧props
对象的keys
,然后循环旧props
对象的keys
,判断新props
对象是否存在对应的属性以及属性值是否相等:
- 如果有任何一个属性判断不通过,则返回
false
,表示props
发生变化,需要执行普通函数组件的更新流程。 - 如果所有判断条件都通过,则返回
true
,表示props
没有发生变化,可以执行进一步的优化策略。
js
if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref)
最后还要注意:这里除了判断props
之外,还判断了ref
对象的变化。
这也就是为何函数组件要使用useRef
来创建ref
对象的原因,以此固定ref
对象的引用地址,这样我们在使用React.memo
方法优化函数组件时,才能够满足这个判断条件,以此进入Bailout
优化策略。
当前我们的simpleMemo
组件满足判断条件:props
对象无属性变化,没有使用ref
,所以ref
对象都为null
。
然后设置全局变量didReceiveUpdate
为false
,表示不需要更新。
ini
didReceiveUpdate = false;
在通过上面的判断条件通过后,还需要继续判断是否存在更新。在第一章节已经讲述过,影响Fiber
节点更新有三个因素:props
,state
和context
,当前仅仅是通过了props
的判断,还需要执行进一步的优化条件判断:
js
// 进一步的优化条件判断
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
workInProgress.lanes = current.lanes;
// 优化策略工作
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
调用checkScheduledUpdateOrContext
方法来判断state
和context
变化影响的更新,这里的判断逻辑和前面的beginWork
中的判断逻辑完全一致,因为它们本来就是相同的Bailout
策略校验,两个不同的地方触发的校验,唯一的区别就是对props
的校验有所不同,所以这里就直接快速说明了,详细的逻辑可以查看《React函数组件性能优化三部曲(一)》。
检查当前Fiber
节点是否存在等待处理的更新:
- 如果存在:则返回
true
,取反后为false
,不满足Bailout
策略,进入普通的函数组件更新流程。 - 如果不存在:则返回
false
,取反后为true
,满足Bailout
策略,进入组件优化策略流程。
当上面的校验都通过后,进入bailoutOnAlreadyFinishedWork
方法,执行进一步的优化程度判断。
bailoutOnAlreadyFinishedWork
查看bailoutOnAlreadyFinishedWork
方法:
js
// packages\react-reconciler\src\ReactFiberBeginWork.new.js
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (current !== null) {
workInProgress.dependencies = current.dependencies;
}
// 标记跳过更新的lanes
markSkippedUpdateLanes(workInProgress.lanes);
// 检查当前节点的子树是否存在更新的工作
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
return null;
}
// 当前Fiber节点没有更新工作,但是它的子树有更新工作,克隆下一层子节点,继续执行子节点的beginWork工作
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
这个方法是Bailout
优化策略的核心方法,它的作用执行进一步的优化程度判断:
- 高度优化:跳过整个子树的更新流程。
- 一般优化:跳过自身节点的更新流程。
当前我们的simpleMemo
组件【Child
】满足高度优化策略,就会直接跳过该组件所有节点的更新流程。
这就是React.memo
方法对函数组件的优化作用,它的实际原理就是在beginWork
之后,组件更新之前,再执行一次Bailout
策略校验。
到此simpleMemo
组件的更新逻辑就执行完成。
memo组件更新
下面我们再来查看一下普通memo
组件的更新流程,此时我们需要对之前案例进行改造,最简单的就是传递forwardRef
组件。
js
// MyFun.js
import { useState, useRef } from 'react'
import NewChild from './NewChild'
export default function MyFun(props) {
console.log('MyFun组件运行了')
const [count, setCount] = useState(1)
const ref = useRef()
function handleClick() {
setCount(count + 1)
}
return (
<div className='MyFun'>
<div>state: {count}</div>
<NewChild name='NewChild' ref={ref}></NewChild>
<button onClick={handleClick}>更新</button>
</div>
)
}
js
// NewChild.js
import React from 'react'
function Child(props, ref) {
console.log('Child组件运行了')
return (
<div className='Child'>
<div ref={ref}>Child子组件</div>
<div>name: {props.name}</div>
</div>
)
}
const NewChild = React.memo(React.forwardRef(Child))
export default NewChild
下面我们直接来到它的更新流程:
js
// react-reconciler/src/ReactFiberBeginWork.new.js
function updateMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
): null | Fiber {
# 加载阶段
if (current === null) {
...
}
# 更新阶段
const currentChild = current.child; // This is always exactly one child
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (!hasScheduledUpdateOrContext) {
const prevProps = currentChild.memoizedProps;
// Default to shallow comparison
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
// 跳过更新,返回原来的节点
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// 不满足bailout策略时
// 根据旧的节点内容及新的props创建新的Fiber节点
const newChild = createWorkInProgress(currentChild, nextProps);
newChild.ref = workInProgress.ref;
newChild.return = workInProgress;
workInProgress.child = newChild;
return newChild;
}
这里我们可以看出普通memo
组件和simpleMemo
组件更新时是基本一致的,也是执行一次Bailout
策略校验:
- 校验通过,则进入
Bailout
策略逻辑。 - 校验不通过,则直接复用旧节点信息以及新的
props
生成新的Fiber
节点,然后执行组件正常的更新逻辑。
这里和simpleMemo
组件更新不同的地方在于,如果传递了自定义compare
比较函数,则调用此方法,如果没有传递这个参数,则会使用默认的shallowEqual
方法进行props
的浅比较。
4,总结
1,当我们使用React.memo(Com, null)
方法来优化我们的函数组件时,会根据传递的参数不同,在react
内部划分为两种组件类型:
simpleMemo
组件- 普通
memo
组件
2,这两个组件具体的区分条件为:
simpleMemo
组件:
React.memo(Com, null)
方法组件参数为简单函数组件,且memo
组件自身无compare
参数,无defaultProps
。
simpleFunctionComponent
简单函数组件条件:必须为函数组件且自身无defaultProps
。
memo
组件:不满足simpleMemo
组件条件的就是普通memo
组件,比如:
React.memo(Com, compare)
方法组件参数为简单函数组件,但memo
组件自身存在compare
参数或者defaultProps
。React.memo(Com, compare)
方法组件参数为forwardRef
组件,此时不管存不存在compare
参数或者defaultProps
,它都是普通memo
组件。
3,这两个组件在更新阶段主要的区别就是对props
校验的不同:
simpleMemo
组件使用react内部默认的shallowEqual
浅比较方法。- 普通
memo
组件可以使用用户传递的自定义的compare
比较函数,如果没有传递则也是使用默认的shallowEqual
方法。
当然这两个组件在不满足Bailout
策略时,创建子节点的逻辑也些许不同,不过这个对我们来说不是重点。
4,simpleMemo
组件和普通memo
组件可以统称为memo
组件:因为它们优化函数组件的原理是相同的,就是在该节点beginWork
之后,函数组件更新之前,再执行一次Bailout
策略校验,其实就是多给函数组件一次救赎的机会。
结束语
以上就是React.memo
方法优化函数组件的全部内容了,觉得有用的可以点赞收藏!如果有问题或建议,欢迎留言讨论!