概要
React Context は、コンポーネントツリー全体に状態や関数などの値を渡すのに非常に便利な仕組みです。しかし、Context の value に状態(state)を渡す場合、その値が更新されると、その Context を参照している全てのコンポーネントが再レンダリングされるという挙動に注意が必要です。本記事では、その仕組みと対策、そしてベストプラクティスについて解説します。
React Context と Provider の仕組み
React Context は、ツリー全体にデータを提供するための仕組みです。通常、以下のように Context を作成し、Provider コンポーネントで value を渡します。
import { createContext, useContext, useState } from 'react';
const MyContext = createContext({
count: 0,
setCount: () => {},
});
export const MyContextProvider = ({ children }) => {
const [count, setCount] = useState(0);
return (
<MyContext.Provider value={{ count, setCount }}>
{children}
</MyContext.Provider>
);
};
export const useMyContext = () => useContext(MyContext);
この例では、count とその更新関数 setCount を Context に渡しています。
Provider の Value が更新されると再レンダリングされる仕組み
React Context の特徴として、Provider の value が変更されると、その Context を参照しているすべてのコンポーネントが再レンダリングされるという点があります。
例えば、上記の例で setCount により count の値が変わると、MyContext を使用している全てのコンポーネントが再レンダリングされます。これは、Context の value が変化したことを検知し、依存しているコンポーネント全体に最新の value を伝えるためです。
再レンダリングが及ぼす影響と注意点
影響範囲の大きい状態の場合
Provider の value に、アプリケーション全体で頻繁に変更される状態を含めると、以下の問題が発生する可能性があります。
パフォーマンスの低下
すべての消費側コンポーネントが再レンダリングされるため、不要な再描画が多発し、パフォーマンスに悪影響を及ぼす可能性があります。
意図しない再レンダリング
再レンダリングが多くなると、意図しない副作用(例えば、スクロール位置のリセットや、フォームの入力状態が消えるなど)が発生する可能性があります。
コンテキストの value に渡すべき内容の見極め
Provider の value には、本当に複数のコンポーネントで共有する必要があるデータや関数のみを含めることが推奨されます。
例えば、あるコンポーネント固有の状態や、再レンダリングの影響が大きい処理は、Context ではなく個別のコンポーネント内やカスタムフックで管理するほうが望ましいです。
React Context における再レンダリングと影響範囲の考察
React の再レンダリングは、状態や props に変化があった箇所のみが実際に DOM に反映される仕組みになっています。
そのため、Context の value に状態を渡す際も、実際に変更があった部分だけが再描画されるというメリットがあります。しかしながら、Context に渡す値が頻繁に変化する場合は、その Context を参照している全てのコンポーネントが再レンダリングされる可能性があるため、影響範囲を十分に考慮する必要があります。
React の再レンダリングの仕組み
React では、状態(state)やプロパティ(props)が変更されると、その変更が発生したコンポーネントの関数が再実行され、新たな JSX が生成されます。その後、React の差分検出(Reconciliation)プロセスによって、実際に DOM に反映されるのは変更のあった部分のみとなります。
つまり、再レンダリング自体はコンポーネント全体が再評価されるものの、ユーザーに見える更新は必要な部分だけになるため、効率的に動作します。
Context の再レンダリングと影響範囲
Context の Provider に状態を渡すと、その値が変わるたびに、その Context を利用しているすべてのコンポーネントが再レンダリングされます。
たとえば、以下のように Context に複数の状態をまとめて渡している場合:
const MyContext = createContext({
count: 0,
text: '',
// ・・・他の状態
});
count や text に変更があった場合、これらを参照している全コンポーネントが再レンダリングされます。
ただし、React の再レンダリングは変更のあった箇所のみを実際に更新する仕組みになっているため、必要な部分だけが更新され、パフォーマンスが最適化されています。
それでも、影響範囲を考慮して Context にどの状態を含めるべきかは慎重に検討する必要があります。
たとえば、頻繁に更新される状態や、コンポーネントツリー全体に広く影響する状態は、個別のコンポーネント内やカスタムフックで管理し、Context にはあまり渡さない方が望ましい場合があります。
まとめ
React の再レンダリングは、変更があった箇所のみ更新される仕組みであり、無駄な描画は抑えられています。
しかし、Context に状態を渡す場合、該当する Context を参照するすべてのコンポーネントが再レンダリングされるため、更新頻度の高い状態は注意が必要です。
影響範囲を考慮し、必要な状態のみを Context に含める、または個別のコンポーネントやカスタムフックに分割するなどの工夫が求められると思います。