Reactアプリを開発していると、「なんだか動作が重くなってきたな…」と感じることはありませんか?
その原因のひとつとして挙げられるのが「不要な再レンダリング」です。たとえば、「ある子コンポーネントが表示内容を変えていないにもかかわらず、親コンポーネントが更新されたことで巻き込まれて毎回再描画されてしまう」というケースがあります。
こうした無駄な再レンダリングは、アプリのパフォーマンス低下に直結します。
そんなときに役立つのが、Reactのmemo
関数(React.memo()
)です。
この記事では「Reactのmemo
関数(React.memo()
)」について、以下の内容をサンプルコード付きでわかりやすく解説します。
memo
関数とは?memo
関数を使わなかった場合のサンプルコードmemo
関数を使った場合のサンプルコード
memo
関数の注意点useCallback
やuseMemo
を使わなかった場合のサンプルコードuseCallback
と一緒に使った場合のサンプルコードuseMemo
と一緒に使った場合のサンプルコード
memo関数とは?
Reactのmemo関数(React.memo())はコンポーネントの無駄な再レンダリングを防ぐ関数です。
通常、親コンポーネントが再レンダリングされると、その中の子コンポーネントも自動的に再レンダリングされます。子コンポーネントに渡すprops
の内容がまったく同じでも、再レンダリングが発生してしまうのです。
そこでReact.memo()
を使うと、子コンポーネントに渡されたprops
が前回と同じであれば、再レンダリングをスキップし、前回の表示内容をそのまま使ってくれます。
memo関数を使わなかった場合
まず、memo
関数を使わないサンプルコードを見てみましょう。
import { useState } from 'react';
const Child = () => {
console.log('Childコンポーネントが再レンダリングされました');
return <div>子コンポーネント</div>;
};
export default function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>カウント: {count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child />
</div>
);
}
上記のサンプルコードでは、+1
ボタンを押すたびに子コンポーネント(Child
)も再レンダリングされます。子コンポーネント(Child
)の中の表示は一切変わっていないのに、親コンポーネント(App
)が再レンダリングされると子コンポーネント(Child
)も毎回再描画されてしまいます。
memo関数を使った場合
次に、子コンポーネント(Child
)をmemo
関数でラップしたサンプルコードを見てみましょう。
import { memo, useState } from 'react';
const Child = memo(() => {
console.log('Childコンポーネントが再レンダリングされました');
return <div>子コンポーネント</div>;
});
export default function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>カウント: {count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child />
</div>
);
}
上記のサンプルコードでは、+1
ボタンを押して親コンポーネント(App
)が再レンダリングされても、子コンポーネント(Child
)は再描画されません。コンソールに「Childコンポーネントが再レンダリングされました」が表示されないことで確認できます。
memo関数の効果
Reactアプリが大きくなってくると、「不要な再レンダリング」が増えて、動作が重くなることがあります。
たとえば、ある子コンポーネントの表示内容が変わっていないのに、親が再レンダリングされた影響で毎回描画されてしまうようなケースです
こういった「子コンポーネントが受け取るprops
が変わっていないのに再レンダリングされてしまう無駄」を防ぐために使えるのが、memo
関数です。
memo
関数を使えば、子コンポーネントが受け取るprops
が同じなら前回の描画結果を再利用してくれるので、無駄な処理を減らしてアプリのパフォーマンスを改善することができます。
memo関数の注意点
React.memo()
を使えば、props
が変わらない限り子コンポーネントの再レンダリングを防げます。しかし、props
に「関数」や「オブジェクト」を渡す場合は注意が必要です。
なぜなら、JavaScriptでは関数やオブジェクトは毎回新しい参照として扱われるからです。
この問題を解決するには、useCallback
やuseMemo
を使って関数やオブジェクトの参照を固定する必要があります。
useCallbackと一緒に使った場合
useCallback
を使わないサンプルコードを見てみましょう。
import { memo, useState } from 'react';
const Child = memo(({ onClick }) => {
console.log('Childが再レンダリングされました');
return <button onClick={onClick}>子ボタン</button>;
});
export default function App() {
const [count, setCount] = useState(0);
// 毎回新しい関数になる
const handleClick = () => {
console.log('子ボタンがクリックされました');
};
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child onClick={handleClick} />
</div>
);
}
親コンポーネント(App
)が再レンダリングされるたびに、handleClick
関数は毎回新しい関数オブジェクトとして作られます。そのため、子コンポーネント(Child
)にとっては「props
(onClick
)の値が毎回変わっている」と判断されます。よって、memo
関数を使っていても子コンポーネント(Child
)は毎回再レンダリングされてしまうのです。
useCallback
を使って関数オブジェクトの参照を固定した際のサンプルコードを以下に示します。
import { memo, useCallback, useState } from 'react';
const Child = memo(({ onClick }) => {
console.log('Childが再レンダリングされました');
return <button onClick={onClick}>子ボタン</button>;
});
export default function App() {
const [count, setCount] = useState(0);
// useCallbackで関数の参照を固定
const handleClick = useCallback(() => {
console.log('子ボタンがクリックされました');
}, []);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child onClick={handleClick} />
</div>
);
}
useCallback
を使うことで、親コンポーネント(App
)が再レンダリングされても、handleClick
関数の参照が毎回変わらないため、子コンポーネント(Child
)から見ると「props
が前回と同じ」と判断できます。その結果、memo
関数が効いて、子コンポーネント(Child
)の無駄な再レンダリングが防げます。
あわせて読みたい
『useCallback』については下記の記事で詳しく説明しています。興味のある方は下記のリンクからぜひチェックをしてみてください。 続きを見る【React】useCallbackの使い方を分かりやすく解説!
useMemoと一緒に使った場合
useMemo
を使わないサンプルコードを見てみましょう。
import { memo, useState } from 'react';
const Child = memo(({ user }) => {
console.log('Childが再レンダリングされました');
return <div>こんにちは、{user.name}さん!</div>;
});
export default function App() {
const [count, setCount] = useState(0);
// 毎回新しいオブジェクトになる
const user = { name: '太郎' };
return (
<div>
<h1>カウント: {count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child user={user} />
</div>
);
}
親コンポーネント(App
)が再レンダリングされるたびに、user
オブジェクトが毎回新しく作られます。そのため、子コンポーネント(Child
)にとっては「props
(user
)の値が毎回変わっている」と判断されます。よって、memo
関数を使っていても子コンポーネント(Child
)は毎回再レンダリングされてしまうのです。
useMemo
を使ってオブジェクトの参照を固定した際のサンプルコードを以下に示します。
import { memo, useMemo, useState } from 'react';
const Child = memo(({ user }) => {
console.log('Childが再レンダリングされました');
return <div>こんにちは、{user.name}さん!</div>;
});
export default function App() {
const [count, setCount] = useState(0);
// useMemoでオブジェクトの参照を固定
const user = useMemo(() => ({ name: '太郎' }), []);
return (
<div>
<h1>カウント: {count}</h1>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child user={user} />
</div>
);
}
useMemo
を使うことで、親コンポーネント(App
)が再レンダリングされても、user
オブジェクトの参照が毎回変わらないため、子コンポーネント(Child
)から見ると「props
が前回と同じ」と判断できます。その結果、memo
関数が効いて、子コンポーネント(Child
)の無駄な再レンダリングが防げます。
本記事のまとめ
この記事では「Reactのmemo
関数(React.memo()
)」について、以下の内容を説明しました。
memo
関数とは?memo
関数を使わなかった場合のサンプルコードmemo
関数を使った場合のサンプルコード
memo
関数の注意点useCallback
やuseMemo
を使わなかった場合のサンプルコードuseCallback
と一緒に使った場合のサンプルコードuseMemo
と一緒に使った場合のサンプルコード
お読み頂きありがとうございました。