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
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(<App />);
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()
を組み合わせて利用することでパフォーマンスを向上させることができます。