React+TypeScriptを利用した開発の効率を上げるため、社内でReact勉強会を開きました。
講師役に任命されたのでReact開発の基礎を復習し、当日説明しながらツッコミを入れられる中で、コンポーネントのレンダリングとメモ化によるパフォーマンス改善について、理解が正されたことについて書き記してみます。
今までは「useCallback(やuseMemo)によるメモ化を適切に使えばパフォーマンスの低下を抑えることができる」というざっくりとした認識でした。
また、「アロー関数は毎回生成されて参照が変わるためpropsに渡すならuseCallbackを使おう」という記述も多く見られました。
そのため、
子コンポーネントに渡すコールバック関数をuseCallbackでメモ化しておけば、propsの参照が変化しないので子コンポーネントの再レンダリングは実行されない
と思っていたのですが、この私の考えは厳密には誤りでした。
Reactコンポーネントのレンダリングは2つのステップに分けて考えることができます。
これらは関数コンポーネントで言えばその関数の実行、クラスコンポーネントならrenderの実行にあたります。
- 計算によってReact要素(仮想DOM)を生成する
- React要素をDOM要素に変換する
レンダリングは目に見えるDOM要素の生成に加えて、前段階の計算も含んでいます。
Reactは再計算の結果であるReact要素を比較して、変化した箇所のDOM要素のみを生成・更新するのです。
ちなみに、JavaScriptのオブジェクトであるReact要素よりもDOM要素の生成の方が処理コストが高いです。
次に、レンダリングが実行される条件は次の通りです。
- 親コンポーネントがレンダリングされたとき
- propsが変化したとき
- stateが変化したとき
条件①により、propsやstateが変化していなくても、その親コンポーネントがレンダリングされれば、そのコンポーネントも再レンダリングされます。
したがって、useMemoやuseCallbackを用いたメモ化による効果は、主にReact要素生成の計算を減らすことであり、再レンダリングを抑えることではありませんでした。
(再レンダリングを防ぐにはReact.memoを使う方法があります)
- useMemoやuseCallbackによるメモ化を行っても、子コンポーネントの再レンダリング自体は行われる。
- メモ化によって再レンダリングの計算コストを減らすことができる。
クリーンな開発環境を準備するため、React+TypeScript+ESLint+Prettierの構成を別マシンに一からインストールしました。
パッケージマネージャにはyarnを使いましたが、ネットの記事に従ってnpx create-react-appでインストールを進めると…そこにはpackage-lock.jsonの姿が!
npmのロックファイルなので、yarnでESLintやPrettierをインストールする前に削除しておきます。
一度そのままにしてインストールを続けていたらバージョン関係のエラーが出まくりました。