ReactにおけるmemoとuseCallbackによるパフォーマンス最適化

Reactコンポーネントのレンダリングメカニズム

Reactでは以下の場合にコンポーネントが再レンダリングされます:

  • 親コンポーネントが再レンダリングされた場合
  • コンポーネント自身のstateが変更された場合

不要なレンダリングを避けるために、Reactはコンポーネント、関数、および関数の実行結果をキャッシュする機能を提供しています。

React.memoによるコンポーネントのメモ化

React.memoは高階コンポーネントで、propsが変更されない限りコンポーネントの再レンダリングを防ぎます。

memo(Component, arePropsEqual?)

パラメータ説明:

  • Component:メモ化するコンポーネント
  • arePropsEqual:カスタム比較関数(オプション)

基本的な使用例

import { useState, memo } from "react";

const MemoizedChild = memo(function ChildComponent() {
  console.log("子コンポーネントがレンダリングされました");
  return <div>子コンポーネント</div>;
});

function ParentComponent() {
  const [counter, setCounter] = useState(0);
  
  return (
    <div>
      {counter}
      <button onClick={() => setCounter(counter + 1)}>インクリメント</button>
      <MemoizedChild />
    </div>
  );
}

export default ParentComponent;

Propsの比較メカニズム

基本型のprops

const MemoizedChild = memo(function ChildComponent({ value }) {
  console.log("子コンポーネントがレンダリングされました");
  return <div>値: {value}</div>;
});

function ParentComponent() {
  const [counter, setCounter] = useState(0);
  const fixedValue = 100;
  
  return (
    <div>
      {counter}
      <button onClick={() => setCounter(counter + 1)}>インクリメント</button>
      <MemoizedChild value={fixedValue} />
    </div>
  );
}

参照型のpropsとuseMemo

import { useState, memo, useMemo } from "react";

const MemoizedChild = memo(function ChildComponent({ items }) {
  console.log("子コンポーネントがレンダリングされました");
  return <div>アイテム数: {items.length}</div>;
});

function ParentComponent() {
  const [counter, setCounter] = useState(0);
  
  const itemList = useMemo(() => {
    return [1, 2, 3, 4, 5];
  }, []);
  
  return (
    <div>
      {counter}
      <button onClick={() => setCounter(counter + 1)}>インクリメント</button>
      <MemoizedChild items={itemList} />
    </div>
  );
}

useCallbackによる関数のメモ化

useCallbackは関数をキャッシュし、依存配列の値が変更された時のみ関数を再作成します。

useCallback(fn, dependencies)

基本的な使用例

import { useState, memo, useCallback } from "react";

const TextInput = memo(function Input({ onChange }) {
  console.log("入力コンポーネントがレンダリングされました");
  return <input type="text" onChange={(e) => onChange(e.target.value)} />;
});

function App() {
  console.log("親コンポーネントがレンダリングされました");
  const [counter, setCounter] = useState(0);
  
  const handleInputChange = useCallback((value) => {
    console.log(value);
  }, []);
  
  return (
    <div>
      <TextInput onChange={handleInputChange} />
      {counter}
      <button onClick={() => setCounter(counter + 1)}>インクリメント</button>
    </div>
  );
}

export default App;

依存配列を使用する例

import { useState, memo, useCallback } from "react";

const TextInput = memo(function Input({ onChange }) {
  console.log("入力コンポーネントがレンダリングされました");
  return <input type="text" onChange={(e) => onChange(e.target.value)} />;
});

function App() {
  console.log("親コンポーネントがレンダリングされました");
  const [counter, setCounter] = useState(0);
  
  const handleInputChange = useCallback((value) => {
    console.log(counter, '入力値:', value);
  }, [counter]);
  
  return (
    <div>
      <TextInput onChange={handleInputChange} />
      {counter}
      <button onClick={() => setCounter(counter + 1)}>インクリメント</button>
    </div>
  );
}

依存配列にcounterを指定しているため、counterが変更されるたびに関数が再作成され、子コンポーネントも再レンダリングされます。

タグ: React Hooks memo useCallback パフォーマンス最適化

6月4日 18:01 投稿