React Hooksを元気に使っていたのですが、useMemo
で必要以上にメモされるというトラブルが起きてしまいました。
React.useMemo
とは
Reactのrender
関数内で、その場で作った無名関数を使うと、render
のたびに関数が再生成されてしまって、不必要に下位コンポーネントのrender
が必要となってしまいます。また、配列の絞り込みなど複雑な操作を毎回行うと、そのコストもかさみます。
そこで使えるHookが、React.useMemo
です。useMemo(() => 欲しい値, [依存する変数の配列])
のようにすることで、依存する変数の配列
が変化したときだけ無名関数を実行して欲しい値
を生成するようになります。
なお、コールバック関数の場合、通常「コールバック関数を作るコストの削減」より「関数を不必要に再生成しない」ということが求められますので、コールバック関数を直接指定する形のReact.useCallback(() => {...}, [依存する変数の配列])
というバージョンもあります。
依存する変数について
最後に「依存する変数の配列」を指定しますが、これらのうちどれかが変更されると、値の再生成が行われます。
- 内部から(直接・間接を問わず)呼び出している、
React.useState
の更新関数 - 内部から(直接・間接を問わず)参照している、
props
の値
などは比較的わかりやすいかと思いますが、実は「内部から(直接・間接を問わず)参照しているReact.useState
の値変数」も、ここに入れる必要があります。
入れ忘れた失敗
React.useState
の値変数を書かずにReact.useCallback
を適用したところ、取得するstate
値が最初の値から全く変化しない、という事態に見舞われました。
「実行のたびにstate
値を参照するはずなのに」と思ったのですが、よくよく考えればコールバックから参照する変数がクロージャを生成するために、キャッシュされた関数を使った時にはキャッシュ時のstate
値を参照する結果となって、今の値が取れない、ということになってしまうのでした。
function HookComponent(){
const [value, setValue] = React.useState('');
const handleChange = React.useCallback(
e => setValue(e.target.value), [setValue]
);
// valueを書き換え対象に入れていないので、前のvalueを掴んだままとなる
const handleClick1 = React.useCallback(
() => alert(value), []
);
// valueの変化に追随して、関数も作り直される
const handleClick2 = React.useCallback(
() => alert(value), [value]
);
// Hook定義完了
return(
<div>
<input type="text" value={value}
onChange={handleChange}
/>
<br />
<button type="button"
onClick={handleClick1}
>
valueなし
</button>
<button type="button"
onClick={handleClick2}
>
valueあり
</button>
</div>
);
}