2020-1-31

[React] depsに配列、オブジェクト、関数を指定する際は、レンダリング毎に再生成されてないか気をつけようって話

技術

問題のあるケース

このプログラムで、カウントの2倍の数である doubleCount はcountに変化があった時にのみ計算されてほしいが、サンプルでは親コンポーネントが再レンダリングされた際にも計算されてしまっている。(consoleを開いてボタンを押していけばわかる)

これはなぜかと言うと、 doubleCount の算出に使用している double 関数が親コンポーネントから渡されており、この関数はレンダリング毎に新しく作られているからである。

React Hooksのメモ化のための第二引数の配列にオブジェクトを突っ込んだときの挙動について

オブジェクトが更新される際に常に新しいオブジェクトが返されていれば発火する

上の記事にある通り、depsに関しては Object.is にて変化があるか判定される。Object.isは中身が同じでも参照先が異なっていれば異なるとみなされるので、結果今回のように親子コンポーネントの再レンダリング毎にdepsに変化があるとみなして再計算されてしまった。

解決法

まず、depsを書く必要がある場合は「配列・オブジェクト・関数」を指定していないか注意する。指定する必要がある場合は今回の問題が発生する可能性があるので以下の選択肢を取る。

  1. double関数をuseCallbackで生成する
  2. doubleをdepsから消す

ただ、正直どちらもきつい。

今回の問題はdouble関数が変化が無いのに参照先が変わってしまっていることであるため、useCallbackを使うことで参照先が変わらないようにすることが正攻法であるように思える。

ただ、この手法をとると後から見た時に「なんでここでuseCallbackを使っているの?」となってしまう。別コンポーネントの問題に起因しているためかなりわかりにくい。仮に今回の例で言うところの SampleComponent が不要になって削除された後でもおそらくuseCallbackは残り続けるであろうことも予想される。

じゃあdepsから消せばいいのでは?となるが、それをする場合ESLintのreact-hooks/exhaustive-depsを無効化する必要がある。これをしてしまうと後からuseMemoの内部処理が変わった際に、depsの変更漏れが発生してしまう可能性がある。

いっそのこと「外部に渡す配列・オブジェクト・関数に関してはuseMemo/useCallbackを使用する」とルール化するのも手な気がする。シンプルなルールだし。