useCallback を使うと不必要なレンダリングを避けることができます。この記事では、React.memo + useCallback を使った例を確認します。
import React, { useState, useCallback, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
const [toggle, setToggle] = useState(false);
const multiplication = () => {
return count * count;
};
return (
<div className="App">
{count}
<input
type="number"
value={count}
onChange={(e) => setCount(parseInt(e.target.value))}
/>
<button onClick={() => setToggle(!toggle)}> toggle </button>
<Child multiplication={multiplication} />
</div>
);
}
const Child = ({ multiplication }) => {
const [value, setValue] = useState(0);
useEffect(() => {
console.log('Child');
setValue(multiplication);
})
return <p>{value}</p>
}
export default App;
19: toggle の更新のたびに関係の無い、子コンポーネントが再レンダリング(29: Child が表示)されます。子コンポーネントが計算量が高いと仮定して、これを避けるための useCallback の書き方の例を確認します。
useCallback とは
コールバックをメモ化したものを返し、その関数は依存配列の要素のいずれかが変化した場合にのみ変化します。これは、不必要なレンダーを避けるために、参照の同一性を見るよう最適化されたコンポーネントにコールバックを渡す場合に便利です。
React.memo + useCallback
import React, { memo, useState, useCallback, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
const [toggle, setToggle] = useState(false);
const multiplication = useCallback(() => {
return count * count;
}, [count]);
return (
<div className="App">
{count}
<input
type="number"
value={count}
onChange={(e) => setCount(parseInt(e.target.value))}
/>
<button onClick={() => setToggle(!toggle)}> toggle </button>
<Child multiplication={multiplication} />
</div>
);
}
const Child = memo(({ multiplication }) => {
const [value, setValue] = useState(0);
useEffect(() => {
console.log('Child');
setValue(multiplication);
})
return <p>{value}</p>
})
export default App;
1: memo, useCallback をインポート
7~9: useCallback はメモ化した関数を返します。第2引数に依存している値の配列を指定します。count の値が更新した場合にのみ multiplication 関数は更新します。
25~34: コンポーネントを memo で囲みます。
memo は引数に更新がない場合は、コンポーネントのレンダーをスキップし、最後のレンダー結果を再利用します。
つまりメモ化した関数を引数に持つ Child コンポーネントは、count が更新された場合に再レンダリングされます。
以上です。