banner
IWSR

IWSR

我永远喜欢志喜屋梦子!

React.memo

TLNR#

  1. React.memo は、渡されたコンポーネントに REACT_MEMO_TYPE のタグを付けることで、各更新前に渡された props を浅く比較(または compare 関数を使用して新旧 props を比較)し、元の fiber コンポーネントを再利用するかどうかを決定します。
  2. 現在のコンポーネント内に更新がある場合、memo コンポーネントは比較をスキップし、新しい fiber を直接生成します。

说明#

分析は以下のコードに基づいています。

import React, { useState, memo } from 'react';

const isEqual = (prevProps, nextProps) => {
  if (prevProps.number !== nextProps.number) {
      return false;
  }
  return true;
}

const ChildMemo = memo((props = {}) => {
  console.log(`--- memo re-render ---`);
  return (
      <div>
          <p>number is : {props.number}</p>
      </div>
  );
}, isEqual);

function Child(props = {}) {
  console.log(`--- re-render ---`);
  return (
      <div>
          <p>number is : {props.number}</p>
      </div>
  );
};

export default function ReactMemo(props = {}) {
    const [step, setStep] = useState(0);
    const [count, setCount] = useState(0);
    const [number, setNumber] = useState(0);

    const handleSetStep = () => {
        setStep(step + 1);
    }

    const handleSetCount = () => {
        setCount(count + 1);
    }

    const handleCalNumber = () => {
        setNumber(count + step);
    }


    return (
        <div>
            <button onClick={handleSetStep}>step is : {step} </button>
            <button onClick={handleSetCount}>count is : {count} </button>
            <button onClick={handleCalNumber}>numberis : {number} </button>
            <hr />
            <Child step={step} count={count} number={number} /> <hr />
            <ChildMemo step={step} count={count} number={number} />
        </div>
    );
}

以下は左から右にボタンをクリックした後の出力です。memo でラップされたコンポーネントは compare 関数を満たす場合にのみ更新され、もう一方は毎回再レンダリングされることがわかります。

Nov-24-2022 00-07-23

从 React.memo 开始#

ChildMemo にブレークポイントを設定すると、memo 内に入ります。

export function memo<Props>(
  type: React$ElementType,
  compare?: (oldProps: Props, newProps: Props) => boolean,
) {
  ... 削除されたdevロジック
  const elementType = {
    $$typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
  ... 削除されたdevロジック
  return elementType;
}

ロジックは非常にシンプルで、ラップされたコンポーネントに REACT_MEMO_TYPE というタグを付けます。このステップは WIP ツリーを構築する際(beginWork)に影響を与えます —— つまり、MemoComponent に対するロジックに入ります。

beginWork 如何处理 MemoComponent#

beginWork 内の case MemoComponent にブレークポイントを設定し、任意の更新をトリガーすると、memo のロジックに入ります(名前が updateMemoComponent と呼ばれていても、マウント段階でもこの関数に入ってノードを作成します)。

image
function updateMemoComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes,
): null | Fiber {
  // currentがnullの場合、マウント段階のことに対応します
  // currentがnullの場合は、対応するfiberノードを作成します(createFiberFromTypeAndProps)、比較対象がないので直接作成します
  if (current === null) {
    const type = Component.type;
    ...
    ... 削除されたdevロジック
    const child = createFiberFromTypeAndProps(
      Component.type,
      null,
      nextProps,
      workInProgress,
      workInProgress.mode,
      renderLanes,
    );
    child.ref = workInProgress.ref;
    child.return = workInProgress;
    workInProgress.child = child;
    return child;
  }
	... 削除されたdevロジック
  // ここではcurrentノードが存在するため、更新ロジックに入ります
  const currentChild = ((current.child: any): Fiber); // これは常に正確に1つの子です
  // ここで言及する必要があるのは、renderLanesは現在のレンダリング優先度であり、優先度がrenderLanesと同じ更新のみが今回のレンダリングで処理されることです
  // 更新が発生するたびに、現在の更新のレーンがroot.pendingLanesにマークされ、root.pendingLanesの最高優先度がrenderLanesになります
  // ここでReact Hooks: useStateを確認できます。その記事では詳細な説明があります
  // 下の関数は、現在のfiberノード上でrenderLanesと同じ優先度のupdateインスタンスが存在するかどうかをチェックします。存在する場合、その更新は今回のレンダリング内で処理される必要があります。つまり、渡されたpropsが変更されていなくても(またはcompare関数がtrueを返しても)、このコンポーネントは依然として更新されます
  // これにより、TLNRの対応する2つ目の結論が得られます
  const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
    current,
    renderLanes,
  );
  // 現在のfiber上に即座に更新される必要があるupdateが存在しない場合、
  // その場合はif内のロジックに入ります
  if (!hasScheduledUpdateOrContext) {
    // これは解決されたdefaultPropsを持つpropsになります、
    // current.memoizedPropsとは異なり、未解決のものです。
    const prevProps = currentChild.memoizedProps;
    // デフォルトで浅い比較を行います
    let compare = Component.compare;
    // compareが存在しない場合、つまりReact.memoの第2引数が渡されていない場合は、デフォルトのshallowEqualが使用されます
    // shallowEqualは浅い比較で、2つのpropsの参照アドレスが変更されたかどうかを比較します
    compare = compare !== null ? compare : shallowEqual;
    if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
      // compareがtrueを返した場合、つまりmemoでラップされたノードは再利用できることを意味し、bailoutOnAlreadyFinishedWorkを呼び出してcurrentツリーのノードを再利用します
      // これによりノードを再生成する必要がなくなり、パフォーマンスが最適化されます
      // bailoutOnAlreadyFinishedWork関数の解析は後で付録に追加します
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
  }
  // React DevToolsはこのフラグを読み取ります。
  workInProgress.flags |= PerformedWork;
  // ここに到達した場合、現在のfiber上に即座に更新される必要があるupdateが存在します
  // その場合はノードを再生成するしかありません
  const newChild = createWorkInProgress(currentChild, nextProps);
  newChild.ref = workInProgress.ref;
  newChild.return = workInProgress;
  workInProgress.child = newChild;
  return newChild;
}
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。