跳到主要内容

认识

2023年12月28日
柏拉文
越努力,越幸运

一、认识


React.memo() 允许你的组件在 props 没有改变的情况下跳过重新渲染。如下所示:

默认比较函数: 默认情况下,React 将使用 Object.is 比较每个 prop

import { memo } from 'react';

function MyComponent(){
return <div>My Component</div>
}

const MemoizedComponent = memo(MyComponent)

自定义比较函数: 如果指定了自定的比较函数, React 将使用自定义比较函数比较每个 prop。如果自定义比较函数返回 true, 该组件将不会重新渲染。

import { memo } from 'react';

function MyComponent(){
return <div>My Component</div>
}

const MemoizedComponent = memo(MyComponent, (oldProps, newProps)=>{
return oldProps === newProps;
})

bailout 性能优化策略 中, 在 props 不变、state 不变、context 不变、type 不变的情况下, 会命中 bailout 策略, 会复用子组件, 子组件不会重新渲染。这时候传递给子组件的 props 引用地址是不变的。但是如果因为 props 或者 state 发生改变, 没有命中 bailout 策略, 会重新生成子组件, 传递给子组件的 props 也会是一个全新的地址。

基于以上原因, React.memo() 的作用是使 bailout 性能优化策略中的 props 的全等比较逻辑变为 props 浅比较。其本质是在子组件和父组件之间增加一个 MemoComponent, MemoComponent 通过 props 的浅比较命中 bailout 策略, 使其 MemoComponent 的子组件不会重新渲染

二、细节


  1. 定义 memo 函数

  2. 创建 fiber 时, 处理 memo 类型 fiber

  3. beginWork 时, 处理 memo 类型 fiber

  4. completeWork 时, 处理 memo 类型 fiber

三、定义 memo 函数


export function memo(type, compare) {
const fiberType = {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare
};

return fiberType;
}

四、创建 fiber 时, 处理 memo 类型 fiber


export function createFiberFromElement(element, lanes) {
const { ref, key, type, props } = element;

let fiberTag = FunctionComponent;

if (typeof type === 'string') {
fiberTag = HostComponent;
} else if (typeof type === 'object') {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break;
default:
break;
}
} else if (type === REACT_SUSPENSE_TYPE) {
fiberTag = SuspenseComponent;
} else if (typeof type !== 'function') {
console.log('未定义的 type 类型', element);
}

const fiber = new FiberNode(fiberTag, props, key);

fiber.ref = ref;
fiber.type = type;
fiber.lanes = lanes;

return fiber;
}

五、beginWork 时, 处理 memo 类型 fiber


beginWork 函数处理如下:

export const beginWork = (workInProgress, renderLane) => {
// bailout 策略
……

workInProgress.lanes = NoLanes;

switch (workInProgress.tag) {
case HostRoot:
return updateHostRoot(workInProgress, renderLane);
case HostText:
return null;
case Fragment:
return updateFragment(workInProgress);
case HostComponent:
return updateHostComponent(workInProgress);
case FunctionComponent:
return updateFunctionComponent(
workInProgress,
workInProgress.type,
renderLane
);
case ContextProvider:
return updateContextProvider(workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(workInProgress);
case OffscreenComponent:
return updateOffscreenComponent(workInProgress);
case MemoComponent:
return updateMemoComponent(workInProgress, renderLane);
default:
console.log('beginWork 未实现的类型');
break;
}
return null;
};

updateMemoComponent 处理如下

function updateMemoComponent(workInProgress, renderLane) {
/**
* @description: 满足 bailout 四要素
* 1. props 浅比较
*/
const current = workInProgress.alternate;
const nextProps = workInProgress.pendingProps;
const Component = workInProgress.type.type;

if (current != null) {
const prevProps = current.memoizedProps;

if (
shallowEqual(prevProps, nextProps) &&
current.ref === workInProgress.ref
) {
didReceiveUpdate = false;
workInProgress.pendingProps = prevProps;

if (!checkScheduledUpdateOrContext(current, renderLane)) {
workInProgress.lanes = current.lanes;
return bailoutOnAlreadyFinishedWork(workInProgress, renderLane);
}
}
}
return updateFunctionComponent(workInProgress, Component, renderLane);
}

shallowEqual 浅比较逻辑如下:

export function shallowEqual(objA, objB) {
if (Object.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;
}

for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!Object.hasOwnProperty.call(objB, currentKey) ||
!Object.is(objA[currentKey], objB[currentKey])
) {
return false;
}
}

return true;
}

六、completeWork 时, 处理 memo 类型 fiber


export const completeWork = workInProgress => {
const newProps = workInProgress.pendingProps;
const current = workInProgress.alternate;

switch (workInProgress.tag) {
……
case MemoComponent:
bubbleProperties(workInProgress);
return;
default:
console.log('completeWork 未实现的类型');
break;
}
};