0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React useCallback()を利用して関数をメモ化する

Last updated at Posted at 2023-04-02

useCallback()

useCallback()は関数自体をメモ化するフックです。
useCallback()はメモ化されたコールバック関数を返します。
React.memo()でラップしたコンポーネントにメモ化されたコールバック関数をpropsで渡すことで不要な再レンダリングを防ぎます。

React.memo()を利用した場合でも、親コンポーネントからコールバック関数をpropsとして受け取った場合、子コンポーネントは再レンダリングしてしまいます。子コンポーネントへpropsで渡された関数の内容が前回のpropsと同じだとしても、コンポーネントが再レンダリングされる度にコールバック関数が再生成されるため、propsの値は同じ(等価)ではないからです。つまり、関数の処理内容は同じであるが、毎回新しい関数を生成していると判断されるため、propsは等価と判断されません。

以下がuseCallback()の基本構文になります。
以下の例の場合、useCallback()の第2引数に設定されている依存配列の要素(x, y)のどちらかに変化があった場合、コールバック関数sampleCallbackFuncを作り直します。依存配列の要素(x, y)に両方変化がない場合、メモ化したコールバック関数sampleCallbackFuncを再利用する。

// コールバック関数sampleCallbackFunc
// コンポーネントが再レンダリングされる度に作り直される
const sampleCallbackFunc = () => {doSomething(x, y)}

// useCallback()の基本構文
//useCallback(コールバック関数, [コールバック関数が依存している要素の配列])
const sampleFunc = useCallback(sampleCallbackFunc, [x, y])

以下のように、useCallback()の第2引数に設定されている依存配列が空の配列の場合、初回レンダリング時に生成した関数sampleCallbackFuncを使い続きます。

// コールバック関数sampleCallbackFunc
// コンポーネントが再レンダリングされる度に作り直される
const sampleCallbackFunc = () => {doSomething(x, y)}

// useCallback()の基本構文
//useCallback(コールバック関数, [コールバック関数が依存している要素の配列])
const sampleFunc = useCallback(sampleCallbackFunc, [])

useCallback()を使用したサンプル

今回作成したサンプルプログラムは、以下のようになります。
チェックボックスを選択し、表示ボタンを押すとチェックされた値が表示されます。

コード解説

index.js

index.js
import { createRoot } from "react-dom/client";

import App from "./App";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(<App />);

App.js

App.js
// useCallback()を使用するためインポートする
import React, { useState, useCallback } from "react";
import "./styles.css";

// チェックボックスの要素を配列で設定する
const items = [
  { id: 1, item: "国語" },
  { id: 2, item: "数学" },
  { id: 3, item: "英語" },
  { id: 4, item: "理科" },
  { id: 5, item: "社会" }
];

const CheckBoxOption = (props) =>

  // map()を用いて配列itemsから1つひとつチェックボックスに設定する
  items.map((item) => {
    return (
      <label key={item.id}>
        <input
          type="checkbox"
          value={item.item}

          // チェックされた場合、またはチェックが外された場合関数handleChangeを実行
          onChange={props.handleChange}
        />
        {item.item}
      </label>
    );
  });

// React.memo()でCheckResultコンポーネントをラップする
// 親コンポーネントからprops(name, text, textChange)を受け取る
// 関数textChangeはuseCallback()によりラップされている
const CheckResult = React.memo((props) => {
  console.log(`${props.name}が選択されました!`);
  return (
    <>
      <p>
        {props.name}: {props.text}
      </p>
      
      // 表示ボタンをクリックすると関数textChangeが実行される
      <button onClick={props.textChange}>表示</button>
    </>
  );
});

const CheckBox = () => {

  // 現在のチェックボックスAで選択されている値を保持する配列valueA
  // valueAを更新する関数setValueA   
  const [valueA, setValueA] = useState([]);

  // 現在のチェックボックスBで選択されている値を保持する配列valueB
  // valueBを更新する関数setValueB   
  const [valueB, setValueB] = useState([]);

  // 現在のチェックボックスAで選択されている値を"、 "区切りで文字列に変換した値textA
  // textAを更新する関数setTextA
  const [textA, setTextA] = useState("");

  // 現在のチェックボックスBで選択されている値を"、 "区切りで文字列に変換した値textB
  // textBを更新する関数setTextB
  const [textB, setTextB] = useState("");

  // チェックボックスAがチェックされた場合、またはチェックが外された場合、関数handleChangeAを実行
  const handleChangeA = (e) => {

    // チェックを外した場合
    if (valueA.includes(e.target.value)) {

      // チェックを外した後、チェックが残っている項目を配列の要素に設定する
      setValueA(valueA.filter((valueA) => valueA !== e.target.value));
    
    // チェックをした場合
    } else {

      // 現在の配列に新たにチェックした要素を追加する
      setValueA([...valueA, e.target.value]);
    }
  };

  // チェックボックスBがチェックされた場合、またはチェックが外された場合、関数handleChangeBを実行
  const handleChangeB = (e) => {

    // チェックを外した場合
    if (valueB.includes(e.target.value)) {

      // チェックを外した後、チェックが残っている項目を配列の要素に設定する
      setValueB(valueB.filter((valueB) => valueB !== e.target.value));

    // チェックをした場合
    } else {

      // 現在の配列に新たにチェックした要素を追加する
      setValueB([...valueB, e.target.value]);
    }
  };

  // 関数textChangeAをuseCallback()でラップする
  const textChangeA = useCallback(() => {

    // 現在のチェックボックスAで選択されている値を保持する配列valueAを"、 "で分割した文字列を
    // 関数setTextAに設定する
    // useCallback()の第2引数の依存配列の要素valueAに変化があった場合、
    // 第1引数の関数を作り直す
    setTextA(valueA.join(""));
  }, [valueA]);

  // 関数textChangeBをuseCallback()でラップする
  const textChangeB = useCallback(() => {

    // 現在のチェックボックスBで選択されている値を保持する配列valueBを"、 "で分割した文字列を
    // 関数setTextBに設定する
    // useCallback()の第2引数の依存配列の要素valueBに変化があった場合、
    // 第1引数の関数を作り直す
    setTextB(valueB.join(""));
  }, [valueB]);

  return (
    <>
      <div>
        <CheckResult
          name="チェックボックスA"
          text={textA}
          textChange={textChangeA}
        />
        <CheckBoxOption handleChange={handleChangeA} />
      </div>
      <div>
        <CheckResult
          name="チェックボックスB"
          textB={textB}
          textChange={textChangeB}
        />
        <CheckBoxOption handleChange={handleChangeB} />
      </div>
    </>
  );
};

export default function App() {
  return <CheckBox />;
}

CheckResutlコンポーネントに渡されるprops.textChangeは関数textChangeがuseCallBack()でラップされているため、第2引数の依存配列に設定しているvalueA、またはvalueBが変更されない場合、第1引数に設定している関数setTextA、またはsetTextBは作り直されない。

例えば、チェックボックスAに変更が加えられた場合、チェックボックスBのvalueBには変更がないため、関数は作り直されない。よって、propsによって渡される値も前回と同じであり画面の再レンダリングは行われない。

ただし、関数textChangeをuseCallback()でラップしなかった場合、チェックボックスBに変更が加えられていなくても、チェックボックスAに変更が加えられてしまうと再レンダリングされてしまう。

styles.css

記述なし

useCallback()とReact.memo()の組み合わせ

useCallback()を単体で利用してもコンポーネントの不要な再レンダリングをスキップすることはできません。
useCallback()でメモ化されたコールバック関数は、React.memo()でメモ化された子コンポーネントへpropsで渡されることで、不要な再レンダリングをスキップすることができます。
useCallback()React.memo()を組み合わせて利用することでパフォーマンスを向上させることができます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?