Reactにはパフォーマンスを最適化する為のAPIが用意されています。具体的にはReact.memo
,useCallback
,useMemo
があります。React.memo
ではコンポーネントをメモ化し、 useCallBack
では関数(コールバック関数)をメモ化。useMemo
は値(関数の結果)のメモ化といった役割があります。
メモ化とは、計算結果を保持し、それを再利用する手法のことです。メモ化によって各コンポーネントで都度計算する必要がなくなるため、パフォーマンスの向上が期待できます。
コールバック関数とは、ある関数を呼び出すときに引数に指定する別の関数のこと。
Reactのレンダリングとは、コンポーネントの本体である関数を評価(実行)すること
まず前提知識として、Reactの再レンダリングの条件を見ていきます。
などが挙げられます。
useMemo
は値(関数の結果)を保持するためのhooksです。
何回やっても結果が同じ場合の値などをメモリに保存(メモ化)して、初回レンダリング以外に行われる際の再レンダリングでは高価な計算を避けることができます。
このhooksを使う事により、不要な再計算をスキップすることができ、パフォーマンスの向上が期待出来ます。
useMemo(() => { 重い処理 }, [依存配列]);
依存配列に指定した変数に変更がない場合、重い処理が実行されないようになる。ちなみに、依存配列の価を複数指定した場合はどちらか一方が変更されたら重い処理が実行される。
依存配列が空なら、何にも依存しないことから、初回1回のみ実行。つまり、依存関係が変わらない場合はキャッシュから値(関数の結果の値)を取ってきます。
import React, { useMemo } from "react";
const App = () => {
// 再レンダリングされるたびに重い処理が実行される
const method = superHeavyProcess(a, b);
//aまたはbが変化したときだけ重い処理が実行される
const method = useMemo(() => {
superHeavyProcess(a, b)
}, [a, b]);
return (
・・・
)
};
export default App;
このようにuseMemo
を使う事により依存配列に指定しているaまたはbが変更されない限り、再レンダリングが行われてもsuperHevyProcess()が実行されなくなります。→ パフォーマンス向上が期待できる。
以下はuseMemo
のBad Caseです。
import React, { useMemo } from "react";
const App = () => {
// Bad Case①
const hoge = useMemo(() => retern "hoge" , [])
// Bad Case②
const method = useMemo(() => {
superLightProcess(a, b)
}, [a, b]);
return (
・・・
)
};
export default App;
useCallBack
では関数(コールバック関数)をメモ化するhooksです。
このhooksを使う事により、関数の再生成を防ぐことができパフォーマンス向上が期待できます。
具体的には「親コンポーネントで作られた関数を子コンポーネント」に渡す際に使います。
子コンポーネントに渡さない関数は関数の再生成が行われないのでuseCallback
を使う必要がありません。
useCallback(() => { callbackFunction }, [依存配列]);
依存配列に指定した変数に変更がない場合、callbackFunctionが実行されないようになる。ちなみに、依存配列の価を複数指定した場合はどちらか一方が変更されたらcallbackFunctionが実行される。依存配列が変わらなければ、前回のcallbackFunctionを再利用する。
依存配列が空なら、コンポーネントがレンダリングされた時に関数が一度生成されますが、以降は同じ関数オブジェクトを再利用するため、再評価されることはありません。
import React, { useState, useCallback } from "react"
const App = () => {
const [count, setCount] = useState<number>(0);
const handleClick = useCallback(() => {
setCount(prevState => prevState + 1);
});
return (
<p>count:{ count }</p>
<Button onClick={handleClick} />
)
}
export default App;
import React, { FC } from "react"
type Props = {
handleClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
}
const Button: FC<Props> = ({ handleClick }) => {
return (
<button onClick={handleClick}>カウントアップ</button>
)
}
export default Button;
このように親コンポーネント(App)で作った関数(handleClick)を子コンポーネント (Button)に渡すときに使います。
React.memo
はコンポーネントをメモ化したいときに使います。親コンポーネントから渡ってきたpropsが変更されていたら再レンダリングされ、変更がなければ再レンダリングしなくなります。
コンポーネント内で複雑な計算や大量のデータの処理を行っている場合や、プロパティが同一であるコンポーネントの再レンダリングを避けたい場合に使用されます。
また、前章で紹介させてもらったuseCallback
と一緒に使うことが推奨されています。
これらを一緒に使用することで、propsが変更された場合にのみ、新しい関数を生成して再レンダリングをトリガーすることができます。つまり、propsが変更されていない場合には、以前にキャッシュされた関数が使用され、不要な再レンダリングを回避できるということです。なので、必ずしも一緒に使わなければならないわけではありませんが、パフォーマンスの向上を目的とする場合は、一緒に使用することをおすすめします。
const MemoChild = React.memo(親コンポーネントからpropsを受け取る子コンポーネント)
import React, { FC } from "react"
type Props = {
message: string
}
const MemoChild: FC<Props> = React.memo(({ message }) => {
return <div>{message}</div>;
});
export default MemoChild;
このようにReact.memo
を使うと、親コンポーネントから渡ってくるmessageに変化がなければMemoChildは再レンダリングしないとなります。
Component definition is missing display name react/display-name
上記のようなエラーが出る場合があります。
このエラーは、React.memo
を使用している場合によく発生します。React.memo
を使用しているときにこのようなエラーが発生する場合、コンポーネントに名前を付けることで解決できます。名前を付ける方法は、以下のように行うことができます。
const MemoChild: FC<Props> = React.memo(({ message }) => {
return <div>{message}</div>;
});
MemoChild.displayName = 'MemoChild';
ここで、displayNameプロパティを使用して、コンポーネントに表示名を指定しています。このようにすることで、React.memoでメモ化されたコンポーネントのエラーが解決されます。
また調べてみると他にも解決策があるそうだったのでこちらの記事を参考にしてみて下さい。
https://qiita.com/tkmd35/items/fb20e1eb36f84dcbc4a1
また、Next.js では、React.memo
が無効になることはありませんが、Next.js が提供する最適化技術の一部が、React.memo が提供する効果と重複していることがあります。そのため、「Next.js で React.memo を使用することがあまり意味がない」場合があります。
Next.jsが提供するSSRやSSGなどの機能により、コンポーネントの初回レンダリング時に必要なデータやプロパティがすでにサーバーサイドで取得され、生成されたHTMLに含まれているため、クライアントサイドでの再レンダリング時には不要なデータの再取得が必要なくなります。これにより、コンポーネントの再レンダリングが高速化され、React.memo
によるパフォーマンスの向上効果が相対的に小さくなる可能性があります。また、Next.jsのプリフェッチング機能により、事前に必要なデータやリソースを取得し、コンポーネントが必要になったときにすぐに利用できるようになるため、React.memo
による再レンダリングの最適化が不要になる場合もあります。
→元々、Next.jsはページプリレンダリングやコンポーネントレンダリングの最適化を提供しているため。
必ずしもReact.memo
を使用する必要がない場合があるということです。ただし、コンポーネントが多くのプロップスを持ち、再レンダリングのオーバーヘッドが高い場合には、React.memo
を使用することが依然として効果的であることがあります。
これらを使うとパフォーマンスが最適化されますが逆に、「使いすぎたり適切に使わなかったりすればパフォーマンスが下がったりする」ので注意が必要です。
これらを使いこなしてReact初学者から中級者、上級者になりましょう。