Reactコンポーネントのレンダリングメカニズム
Reactでは以下の場合にコンポーネントが再レンダリングされます:
- 親コンポーネントが再レンダリングされた場合
- コンポーネント自身のstateが変更された場合
不要なレンダリングを避けるために、Reactはコンポーネント、関数、および関数の実行結果をキャッシュする機能を提供しています。
React.memoによるコンポーネントのメモ化
React.memoは高階コンポーネントで、propsが変更されない限りコンポーネントの再レンダリングを防ぎます。
memo(Component, arePropsEqual?)
パラメータ説明:
- Component:メモ化するコンポーネント
- arePropsEqual:カスタム比較関数(オプション)
基本的な使用例
import { useState, memo } from "react";
const MemoizedChild = memo(function ChildComponent() {
console.log("子コンポーネントがレンダリングされました");
return <div>子コンポーネント</div>;
});
function ParentComponent() {
const [counter, setCounter] = useState(0);
return (
<div>
{counter}
<button onClick={() => setCounter(counter + 1)}>インクリメント</button>
<MemoizedChild />
</div>
);
}
export default ParentComponent;
Propsの比較メカニズム
基本型のprops
const MemoizedChild = memo(function ChildComponent({ value }) {
console.log("子コンポーネントがレンダリングされました");
return <div>値: {value}</div>;
});
function ParentComponent() {
const [counter, setCounter] = useState(0);
const fixedValue = 100;
return (
<div>
{counter}
<button onClick={() => setCounter(counter + 1)}>インクリメント</button>
<MemoizedChild value={fixedValue} />
</div>
);
}
参照型のpropsとuseMemo
import { useState, memo, useMemo } from "react";
const MemoizedChild = memo(function ChildComponent({ items }) {
console.log("子コンポーネントがレンダリングされました");
return <div>アイテム数: {items.length}</div>;
});
function ParentComponent() {
const [counter, setCounter] = useState(0);
const itemList = useMemo(() => {
return [1, 2, 3, 4, 5];
}, []);
return (
<div>
{counter}
<button onClick={() => setCounter(counter + 1)}>インクリメント</button>
<MemoizedChild items={itemList} />
</div>
);
}
useCallbackによる関数のメモ化
useCallbackは関数をキャッシュし、依存配列の値が変更された時のみ関数を再作成します。
useCallback(fn, dependencies)
基本的な使用例
import { useState, memo, useCallback } from "react";
const TextInput = memo(function Input({ onChange }) {
console.log("入力コンポーネントがレンダリングされました");
return <input type="text" onChange={(e) => onChange(e.target.value)} />;
});
function App() {
console.log("親コンポーネントがレンダリングされました");
const [counter, setCounter] = useState(0);
const handleInputChange = useCallback((value) => {
console.log(value);
}, []);
return (
<div>
<TextInput onChange={handleInputChange} />
{counter}
<button onClick={() => setCounter(counter + 1)}>インクリメント</button>
</div>
);
}
export default App;
依存配列を使用する例
import { useState, memo, useCallback } from "react";
const TextInput = memo(function Input({ onChange }) {
console.log("入力コンポーネントがレンダリングされました");
return <input type="text" onChange={(e) => onChange(e.target.value)} />;
});
function App() {
console.log("親コンポーネントがレンダリングされました");
const [counter, setCounter] = useState(0);
const handleInputChange = useCallback((value) => {
console.log(counter, '入力値:', value);
}, [counter]);
return (
<div>
<TextInput onChange={handleInputChange} />
{counter}
<button onClick={() => setCounter(counter + 1)}>インクリメント</button>
</div>
);
}
依存配列にcounterを指定しているため、counterが変更されるたびに関数が再作成され、子コンポーネントも再レンダリングされます。