最近Reactを始めたのですが、不必要なレンダリングが起こっていて、どうやって制御すればいいのかわかりません。
Reactには不必要なレンダリングを防ぐために「memo」や「useCallback」といった便利な仕組みがあります。
今回の内容は次の方におすすめです!
- 不必要なレンダリングを減らしたい
- レンダリングが走るタイミングを知りたい
親コンポーネントが更新されたときに、更新する必要のない子コンポーネントのレンダリングが走らないようにしていきます。
親の変更に関係ない子コンポーネントまでレンダリングが走ると、動作が重くなるからです。
まずは不要なレンダリングを減らすための3つの仕組みを見ておきます。
- memo:親コンポーネントが更新されたときに、子コンポーネントを更新しない
- useCallback:特定の条件下でのみ関数を再生成する
- useMemo:特定の条件下でのみ変数を再生成する
レンダリングが走るケースを知る
初めに「そもそもレンダリングが走るケース」を確認しておきましょう。
- stateの値が更新されたとき
- propsの値が更新されたとき
- 親コンポーネントが更新されたとき
まず、親コンポーネントが更新されたときに、子コンポーネントでレンダリングが走らないようにしていきます。
memo
memoとは、親コンポーネントが更新されたときに、子コンポーネントでレンダリングさせない仕組みです。
使い方
- memoをインポート
- コンポーネント全体をmemoで覆う
import { memo } from "react"; export const Children = memo(() => { return( <div> <h1>子コンポーネント</h1> </div> ); });
具体例
親コンポーネントで設定したボタンをクリックしたときに、数字が増加するケースを考えます。
このとき、子コンポーネントでもレンダリングが走っています。ボタンをクリックするとChildren.jsx内の”子コンポーネント”が出力されていますね。
Parent.jsx
import { Children } from "./Children"; import { useState } from "react" export const Parent = () => { const [count, setCount] = useState(0); constonClickCountUp=()=>setCount(count+1); return( <div> <h1>親コンポーネント</h1> <button onClick={onClickCountUp}>親ボタン</button> <p>{ count }</p> <Children /> </div> ); };
Children.jsx
export const Children = () => { console.log("子コンポーネント"); return( <div> <h1>子コンポーネント</h1> </div> ) }
子コンポーネントに変更がないにも関わらず、レンダリングが発生するのは不要なため、子コンポーネントでレンダリングが起きないようにしていきます。
Children.jsx
import { memo } from "react"; export const Children = memo(() => { console.log("子コンポーネント"); return( <div> <h1>子コンポーネント</h1> </div> ); });
- memoをインポートします。
- コンポーネント全体をmemoで覆います。
ボタンを押してcountを増やしてもコンソールに”子コンポーネント”が打ち出されず、レンダリングされていないのがわかります。
memoを使えば、無駄なレンダリングのすべて解消されるわけではありません。useCallbackを見ていきましょう。
useCallback
useCallbackとは「特定の条件下でのみ関数を再生成する仕組み」です。
実はmemoを使用しても、親コンポーネントが更新されたときに、子コンポーネントでレンダリングが走る場合があります。それは次の2つを満たしたときです。
- 親コンポーネントの関数が子コンポーネントにpropsとして渡されている
- 親コンポーネントの関数が生成される
親コンポーネントでレンダリングが走って読み込まれると、再度関数が生成され、新しい関数がpropsに渡されたと判断されます。
そのため、親コンポーネントでレンダリングが走ったとき、関係する場合のみ関数が更新されるように設定しておかないと、不要な更新が起きてしまいます。
このときの関係する関数のみ変更を感知して実行する仕組みがuseCallbackです。
使い方
- useCallbackをインポート
- 関数をuseCallbackで覆う
- 第一引数にコールバック関数で処理を記述
- 第二引数に監視したい値を記述。第二引数の値に変更があったときに再生成される。空配列の場合は呼びだしのタイミングでのみ生成される
import { useCallback } from "react" export const Parent = () => { const [open, setOpen] = useState(true); const onClickOpen = useCallback(()=>setOpen(false),[setOpen]); return( <div> <Children open={open} onClickOpen={onClickOpen}/> </div> ); };
具体例
まずはuseCallbackを使わないときの動きを確認します。
“子ボタン”を押したら子コンポーネントを非表示にする機能を追加します。
Parent.jsx
import { Children } from "./Children"; import { useState } from "react" export const Parent = () => { const [count, setCount] = useState(0); const [open, setOpen] = useState(true); const onClickCountUp = () => setCount(count+1); const onClickOpen = () => setOpen(false); return( <div> <h1>親コンポーネント</h1> <button onClick={onClickCountUp}>親ボタン</button> <p>{ count }</p> <Children open={open} onClickOpen={onClickOpen}/> </div> ); };
Children.jsx
import { memo } from "react"; export const Children = memo((props) => { const { open,onClickOpen } =props; console.log("子コンポーネント"); return( <> { open ? ( <div> <h1>子コンポーネント</h1> <button onClick={onClickOpen}>子ボタン</button> </div> ) : null } </> ); });
Chirdrenコンポーネントでmemoを利用しているにもかかわらず、親ボタンを押すとコンソールで”子コンポーネント”が出力されています。
親ボタンを押すたびに、onClickOpenが再生成され、子コンポーネントに新しいonClickOpenが渡されていると判断されるため、子コンポーネントでレンダリングが走っています。
子コンポーネントで再レンダリングが走らないようにuseCallbackを使っていきます。
Parent.jsx
import { Children } from "./Children"; import { useState, useCallback } from "react" export const Parent = () => { const [count, setCount] = useState(0); const [open, setOpen] = useState(true); const onClickCountUp = () => setCount(count + 1); const onClickOpen = useCallback(() => setOpen(false), [setOpen]); return ( <div> <h1>親コンポーネント</h1> <button onClick={onClickCountUp}>親ボタン</button> <p>{ count }</p> <Children open={open} onClickOpen={onClickOpen}/> </div> ); };
- useCallbackでonClickOpen関数を覆っている
- 第一引数にコールバック関数で処理を記述
- 第二引数には監視したい値を設定しています。今回はsetOpenに変更があったときのみ、onClickOpenが生成されるようになっています。
“親ボタン”を押してもコンソールに”子コンポーネント”が出力されなくなりました。setOpenに変化がないためonClickOpen関数が再生成されていないためです。
“子ボタン”を押して、子コンポーネントが非表示になると同時にコンソールの出力が増えます。setOpenに変更があったため、onClickOpen関数が再生成されたからです。
useMemo
useMemoとは「useCallbackの変数バージョン」です。特定の条件下でのみ変数が再生成されるように設定できます。
親コンポーネントの変数が子コンポーネントにpropsとして渡されて、親コンポーネントが更新された場合に、子コンポーネントでレンダリングが走らないようにできます。
使い方
使い方はuseCallbackと同様です。
- 第一引数に変数として設定したい値をコールバック関数で設定します。
- 第二引数に監視したい値を配列で設定します。下の例では、設定していないので、最初に生成されたときだけ実行されて、値は10になります。
import { useMemo } from "react" const variable = useMemo(() => 1+9, []);
まとめ
レンダリングが起きるのは次の3つのケース
- stateの値が更新されたとき
- propsの値が更新されたとき
- 親コンポーネントが更新されたとき
親コンポーネントが更新されたときに不要なレンダリングを防ぐ仕組みが次の3つです。
- memo:親コンポーネントが更新されたときに、子コンポーネントを更新しない
- useCallback:特定の条件下でのみ関数を再生成する
- useMemo:特定の条件下でのみ変数を再生成する
不要なレンダリングが減らせるようになりました。あとは実践の中でどのタイミングで使っていけばいいか試していきます!
不要なレンダリングを減らせばそれだけ負荷が軽くなり速度は改善するので、処理が重くなりがちなところでは積極的に使っていきましょう!
コメント