【React】 レンダリングの最適化についてまとめる
はじめに
Reactのレンダリングの最適化の手法(React.memo,useMemo,useCallback)について、
学んだことを簡単にまとめていきたいと思います。
再レンダリングのタイミングについて
Reactで再レンダリングが行われるタイミングは以下3点です。
- 親コンポーネントが再レンダリングされた場合
- stateが更新された場合
- propsが更新された場合
1. 親コンポーネントが再レンダリングされた場合
以下のように、親コンポーネント(Parent)配下に子コンポーネント(Child1〜Child3)が
存在する場合、親コンポーネント(Parent)が更新されるされると、
赤枠で囲んだ子コンポーネント(Child1〜Child3)が再レンダリングされます。
Parent更新前
Parent更新後
2. stateが更新された場合
useState
等を用いてstate(状態)を持った変数が更新されると、
そのstate(状態)をもったコンポーネントが再レンダリングされます。
3. propsが更新された場合
親コンポーネント(Parent)から子コンポーネント(Child)に渡すprops
の中身が更新されると、
子コンポーネント(Child)が再レンダリングされます。
※追記
propsが更新されるのは、親コンポーネントが再レンダリングされた場合のみなので、「1. 親コンポーネントが再レンダリングされた場合」と実質同義となる。
レンダリングの最適化手法について
レンダリングを最適化する手法には以下3つの方法があります。
- React.memo
- useMemo
- useCallback
これら3つの手法を用いてメモ化することで、余計な再レンダリングを防ぐことができます。
それそれの機能について順に解説していきます。
1. React.memo
React.memo
ではコンポーネントのpropsが変更されない限り、再レンダリングは行われません。
つまり、親コンポーネントが再レンダリングされても、渡されるpropsが変更されなければ、
再レンダリングは行われないということです。
React.memoの使用方法は以下の通りです。
import React from "react";
const Component = React.memo(( props ) => { /* 何か記述 */});
このように記述することで、親コンポーネントが再レンダリングされても、propsの中身が更新されない限り、再レンダリングは行われません。
2. useMemo
useMemo
はReact Hooksの1つであり、再レンダリングの際の計算結果をキャッシュするために使用します。
計算結果をキャッシュすることで、stateが変わらなくなるため、再レンダリングを防ぐことができます。
useMemoの使用方法は以下の通りです。
import React, { useState, useMemo } from "react";
const Component = () => {
const [ temp1, setTemp1 ] = useState(0);
const [ temp2, setTemp2 ] = useState(0);
const data = useMemo(() => { /* temp1とtemp2を用いた計算処理 */}, [temp1, temp2]);
return (
<>
// ...省略
</>
);
};
useMemo
の第一引数には変数(値)を返す関数、第二引数には配列を指定し、
配列の中身には更新を行う際のトリガとなる変数を入れます。
3. useCallback
useCallback
はReact Hooksの1つであり、再レンダリングの際の関数定義をキャッシュするために使用します。
関数定義をキャッシュすることで、propsの更新が行なわれなくなるため、再レンダリングを防ぐことができます。
useCallbackの使用方法は以下の通りです。
import React, { useCallback } from "react";
const Child = React.memo(({ handleClick }) => {
return (
<>
<p>子コンポーネント</p>
<button onClick={handleClick}>Child</button>
</>
);
});
const Parent = () => {
const useCallback(handleClick = () => {
// ボタンクリック時の処理
}, []);
return (
<>
<p>親コンポーネント</p>
<Child handleClick={handleClick} />
</>
);
};
useCallback
の第一引数には関数、第二引数には配列を指定し、
配列の中身には関数を再生成する際のトリガとなる変数を入れます。
(空にすると最初の一度のみ関数が生成されます。)
最後に
簡単ながら、Reactのレンダリングの最適化についてまとめてみました。
あまりレンダリングに関しては意識できていない部分があったので、
今後実装する際はこれらのことを意識して実装していきたいと思いました。
参考文献
Discussion
コンポーネントの再レンダリングが発生するタイミングは以下の2つだけです。
なぜなら、 Props が変わる原因は親コンポーネントの再レンダリングだけ、つまり「
親が再レンダリングされる
⊃Props が更新される
」という関係だからです。コンポーネントを
React.memo
で囲うと、「親のコンポーネントが再レンダリングされても、Props が変わっていなければ、再レンダリングをスキップする」 ようになります。なので、再レンダリング発生の条件は以下のように変わります。
なので、
React.memo
で囲われていないコンポーネントに渡すコンポーネントにuseCallback
でメモ化された関数を渡しても、「Props が変わらなくても、親コンポーネントが再レンダリングされた」条件に引っかかってしまうので再レンダリングは発生します。また、
React.memo
にはuseMemo
やuseCallback
を一箇所でつけ忘れただけで破綻する、という欠点があるので、僕は以下のセクションにあるように、のように、コンポーネントの構造を見直すことを第一に考えるほうが良いと思います。
ご指摘ありがとうございます!
確かに仰る通り、Propsが変わる原因は親コンポーネントが再レンダリングされた場合のみなので、「3. propsが更新された場合」は条件になくても問題ありませんね...
失礼いたしました。
勉強になります!
仰る通りコンポーネントの構造を見直す方を優先したいと思います。
指摘いただいた内容は、追記・修正したいと思います!