以前、React.memo
によるReactの表示パフォーマンスの改善についてまとめました。
それに引き続き、今回はReact.memo
とあわせて使われるHooksである、useCallback
とuseMemo
について調べ、レンダリングの挙動について検証しました。
#useCallback
useCallback
はメモ化されたコールバック関数を返すReact Hooksです。
インラインのコールバック関数とそれが依存している値の配列を渡すと、useCallback
はそのコールバック関数をメモ化したものを返し、その関数は依存配列の要素のいずれかが変化した場合にのみ変化します。
useCallback
は主に親コンポーネントで使用し、メモ化されたコールバック関数は子コンポーネントのpropsとして渡します。
子コンポーネントをReact.memo
でエクスポートしていれば、propsの変化がない限り余計なレンダリングは起こりません。
このようにして、Reactの表示パフォーマンスを向上させてくれます。
##検証
Log nameを押すとconsole.logに文言が出力され、Increase Count1(2)を押すとカウントが表示されるような画面をつくりました。
レンダリング時にfunctions
へlogName
関数が追加されるようにし、useCallback
の使用有無でその挙動がどのように変わるのかを調べました。
###useCallbackを使わない場合
画面の実装は以下のようになります。
import React from 'react';
import logo from './logo.svg';
import './App.css';
const functions = new Set();
const App = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const incrementCount1 = () => setCount1(count1 + 1);
const incrementCount2 = () => setCount2(count2 + 1);
const logName = () => console.log('Yihua');
functions.add(logName); //レンダリングの度にfunctionsインスタンスにlogName関数が追加される
console.log(functions);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
Count1: {count1}
<button onClick={incrementCount1}>Increase Count1</button>
Count2: {count2}
<button onClick={incrementCount2}>Increase Count2</button>
<button onClick={logName}>Log name</button>
</header>
</div>
);
};
export default App;
Increase Count1(もしくは2)を押すとState(count1, count2)の変化が起こるため、カウントが増える度に再レンダリングされます。
その結果、レンダリングの度にfunctions
にはlogName
関数が追加されています。
###useCallbackを使う場合
const logName = () => console.log('Yihua');
と記述していた箇所をuseCallback
で書き換えます。
このとき、第2引数には空配列を与えているので、初期表示でメモ化を行った後に関数の再生成は起こりません。
const logName = useCallback(() => console.log('Yihua'), []);
その結果、Increase Count1(2)を何回押しても、functions
にはlogName
関数は追加されていきません(Set(1)じゃなかった理由は調査中)
#useMemo
useCallback
ではコールバック関数のメモ化を行いましたが、useMemo
は値のメモ化を行います。
“作成用” 関数とそれが依存する値の配列を渡し、依存配列の要素のいずれかが変化した場合にのみメモ化された値を再計算します。
この最適化によりレンダリングごとにコストの高い計算が実行されるのを避けることができます。
useMemo
もuseCallback
と同様に、React.memo
とあわせて使われます。
##検証
Increase Count1を押したときだけdoSomethingComplicated
関数が実行され、計算結果がcomplexValueとして表示されるような画面を実装しました。
###useMemo
を使わない場合
doSomething
関数を追加して、complexValue: {doSomethingComplicated()}
として表示します。
//レンダリングの度に実行される
const doSomethingComplicated = () => {
console.log('I am computing something complex');
return ((count1 * 1000) % 12.4) * 51000 - 4000;
};
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
Count1: {count1}
<button onClick={incrementCount1}>Increase Count1</button>
Count2: {count2}
<button onClick={incrementCount2}>Increase Count2</button>
complexValue: {doSomethingComplicated()}
<button onClick={logName}>Log name</button>
</header>
</div>
);
するとレンダリングのたび(Increase Count1だけではなくIncrease Count2を押しても)にdoSomethingComplicated
内のconsole.logが実行されます。
###useMemo
を使う場合
doSomethingComplicated
をuseMemo
で書き換えると以下のようになります。
第2引数に与えたcount1の値が変化したときだけ新しい値を返すようになります。
const doSomethingComplicated = useMemo(() => {
console.log('I am computing something complex');
return ((count1 * 1000) % 12.4) * 51000 - 4000;
}, [count1]);
また、doSomethingCompleted
は値を返すようになるので、以下の箇所も書き換えます。
complexValue: {doSomethingComplicated}
こうすることで、Increase Count2をいくら押してもdoSomethingCompleted
は実行されないようになります。
#参考資料