Reactコンポーネント間の通信に関する解説

目次

はじめに

Reactは、UIを独立した再利用可能な部品、すなわちコンポーネントに分割して構築するためのJavaScriptライブラリです 1。このコンポーネントベースのアーキテクチャは、複雑なアプリケーションをより管理しやすく、再利用性の高い形で開発することを可能にします。しかし、これらの独立したコンポーネントが連携し、動的でインタラクティブなUIを構築するためには、効果的な通信が不可欠です 2。ユーザーの操作やアプリケーションの状態変化に応じて、異なるコンポーネント間で情報を共有し、UIを適切に更新する必要があります。本稿では、Reactにおける主要なコンポーネント間の通信方法について詳しく解説します。具体的には、親コンポーネントから子コンポーネントへのデータ伝達に用いられるProps、子コンポーネントから親コンポーネントへのイベント通知やデータ伝達に用いられるコールバック関数、兄弟コンポーネント間など離れたコンポーネント間でのデータ共有に役立つContext APIやグローバル状態管理ライブラリ、そしてuseRefを用いたコンポーネント間の直接的な通信について掘り下げていきます。これらの通信方法の基本的な概念から、具体的な使用例、そして開発におけるベストプラクティスと注意点までを網羅的に解説することで、Reactアプリケーション開発におけるコンポーネント間の効果的な連携を実現するための理解を深めることを目指します。

Reactコンポーネント間の通信の基本的な概念

コンポーネントとは何か

ReactアプリケーションのUIは、独立して再利用可能な部品であるコンポーネントによって構成されます 1。コンポーネントは、概念的にはJavaScriptの関数に似ており、任意の入力(Propsと呼ばれます)を受け取り、画面に表示されるべきものを記述するReact要素を返します 1。この関数のような性質を持つため、コンポーネントに入力が与えられると、それに基づいてUIの一部が描画されるという関係性を理解することができます。Reactでは、UIを小さなコンポーネントに分割することで、それぞれの部品を独立して開発、テスト、保守することが可能になり、結果として複雑なアプリケーション全体の管理が容易になります。

コンポーネントの定義方法には、主に2つの形式があります。1つはJavaScriptの関数として定義する関数コンポーネントです 1。関数コンポーネントは、Propsを受け取る引数を持つJavaScript関数として記述され、JSXを返します。もう1つは、ES6のクラスとして定義するクラスコンポーネントですが、近年では関数コンポーネントとHooksの組み合わせがより一般的になっています 1。Hooksは、関数コンポーネント内で状態管理やライフサイクル機能を利用するための仕組みであり、より簡潔で直感的なコード記述を可能にします。どちらの形式のコンポーネントも、親からデータを受け取り、自身のUIをレンダリングするという基本的な役割は共通しています。

コンポーネントツリーとデータの流れ

Reactアプリケーションは、コンポーネントが階層的に組み合わさったコンポーネントツリーとして表現されます 1。通常、アプリケーションの最上位にはルートコンポーネントが存在し、そこから子コンポーネント、孫コンポーネントへと階層が深くなっていきます 1。この階層構造を理解することは、アプリケーション内でのデータの流れを把握する上で非常に重要です。

Reactにおけるデータの流れは、原則として単方向です 6。これは、データが通常、親コンポーネントから子コンポーネントへとPropsを通じて一方的に流れるという原則を指します 3。この単方向のデータフローにより、アプリケーションの状態変化を追跡しやすくなり、予期せぬ副作用を減らすことができます。子コンポーネントが親コンポーネントに何らかの変更を通知したり、データを渡したりする必要がある場合には、親コンポーネントからPropsとして渡されたコールバック関数が利用されます 3。子コンポーネント内で特定のイベントが発生した際にこのコールバック関数を実行することで、親コンポーネントに情報を伝達し、親コンポーネントの状態を更新させることができます。そして、親コンポーネントの状態が更新されると、その変更が再びPropsを通じて子コンポーネントへと伝播していくというサイクルが繰り返されます。

なぜコンポーネント間の通信が必要なのか

コンポーネント間の通信は、Reactアプリケーションが動的でインタラクティブなUIを提供するために不可欠です 2。異なるコンポーネント間で状態を共有し、更新することで、ユーザーの操作に応じたUIの変化や、アプリケーションの状態に応じた表示の切り替えなどを実現できます 3。例えば、あるコンポーネントでユーザーが入力したデータに基づいて、別のコンポーネントで検索結果を表示するといった連携は、コンポーネント間の通信によって可能になります。

また、複雑なUIを構築し、管理する上でも、コンポーネント間の通信は重要な役割を果たします 1。UIを小さな独立したコンポーネントに分割することで、それぞれのコンポーネントは特定の機能に特化し、責務が明確になります。しかし、これらのコンポーネントが連携してアプリケーション全体の機能を実現するためには、互いに情報を交換し、状態を同期させる必要があります。適切なコンポーネント間の通信を行うことで、コードの再利用性が高まり、保守性も向上します。もしコンポーネント間の通信がなければ、それぞれのコンポーネントは孤立した存在となり、アプリケーション全体としての一貫性や連携性を保つことが困難になります。

親コンポーネントから子コンポーネントへのデータ伝達(Props)

Propsの基本的な使い方

親コンポーネントから子コンポーネントへデータを伝達する最も基本的な方法は、Props(プロパティ)を使用することです 1。Propsは、親コンポーネントが子コンポーネントをレンダリングする際に、HTMLの属性のようにして渡すことができます。子コンポーネントは、このPropsを通じて親コンポーネントから様々なデータを受け取ることができます。

親コンポーネントから子コンポーネントへデータを渡す際には、子コンポーネントのJSXタグ内に、属性名と値を記述します 6。この値には、文字列、数値、真偽値などのプリミティブ型だけでなく、オブジェクトや配列などの複合型、さらには関数などのJavaScriptのあらゆる値を指定することができます 6

子コンポーネントでPropsを受け取る方法はいくつかあります。関数コンポーネントの場合、関数の引数としてpropsオブジェクトを受け取り、そのオブジェクトのプロパティとして渡されたデータにアクセスすることができます 1。より一般的な方法としては、分割代入を利用して、必要なPropsを直接引数として受け取る方法があります 1。この方法を用いると、コードがより簡潔になり、可読性が向上します。JSX内では、このようにして受け取ったPropsの値を、波括弧{}で囲むことでJavaScriptの式として埋め込むことができます 6

様々なデータ型のPropsの受け渡し

Propsを通じて、様々なデータ型の値を子コンポーネントに渡すことができます 6。文字列や数値、真偽値などのプリミティブ型は、そのまま属性値として記述することができます。

JavaScript

// 親コンポーネント
function ParentComponent() {
  const message = “Hello from Parent!”;
  const count = 123;
  const isActive = true;
  return (
    <ChildComponent message={message} count={count} isActive={isActive} />
  );
}

// 子コンポーネント
function ChildComponent({ message, count, isActive }) {
  return (
    <div>
      <p>{message}</p>
      <p>Count: {count}</p>
      <p>Is Active: {isActive? ‘Yes’ : ‘No’}</p>
    </div>
  );
}

オブジェクトや配列などの複合型も、Propsとして渡すことができます 6。ただし、これらのデータをPropsとして渡す場合、Propsは不変であるという原則を理解しておく必要があります 1。子コンポーネント内でこれらのオブジェクトや配列を直接変更しようとすると、予期せぬ動作を引き起こす可能性があります。もし子コンポーネントがこれらのデータを変更する必要がある場合は、親コンポーネントにその旨を通知し、親コンポーネントの状態を更新してもらう必要があります。

JavaScript

// 親コンポーネント
function ParentComponent() {
  const user = { name: "Taro", age: 30 };
  const items =;
  return (
    <ChildComponent user={user} items={items} />
  );
}

// 子コンポーネント
function ChildComponent({ user, items }) {
  return (
    <div>
      <p>Name: {user.name}, Age: {user.age}</p>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

また、関数をPropsとして渡すことも非常に重要です。これは、後述する子コンポーネントから親コンポーネントへのデータ伝達、すなわちコールバック関数として利用されます 6

Propsの不変性

Reactにおいて、Propsは子コンポーネントから見て読み取り専用であり、直接変更することはできません 1。この不変性という原則は、データの流れを予測可能にし、アプリケーションの状態管理を容易にするために非常に重要です。もし子コンポーネントが受け取ったPropsを自由に書き換えることができてしまうと、どのコンポーネントがいつ、どのようにデータを変更したのかを追跡することが困難になり、アプリケーションの挙動が複雑化してしまいます。

Propsの変更は、常に親コンポーネントによってのみ行われます 6。子コンポーネントが何らかのデータを変更する必要がある場合、直接Propsを操作するのではなく、親コンポーネントにその意図を伝え、親コンポーネントが自身の状態を更新し、その結果として新しいPropsを子コンポーネントに渡すという流れになります。このパターンを守ることで、データの流れが常に一方向となり、アプリケーションの状態変化を理解しやすくなります。

children propについて

children propは、Reactにおける特別なPropsの一つです 6。親コンポーネントのJSXタグ内に記述された子要素(HTML要素や他のReactコンポーネント)は、自動的にprops.childrenとして子コンポーネントに渡されます。この仕組みを利用することで、再利用性の高いコンポーネントを作成することができます。

JavaScript

// 親コンポーネント
function ParentComponent() {
  return (
    <Card>
      <h1>ようこそ!</h1>
      <p>これはカードのコンテンツです。</p>
    </Card>
  );
}

// 子コンポーネント(Card)
function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

上記の例では、ParentComponent内で<Card>コンポーネントのJSXタグ内に記述された<h1>要素と<p>要素が、Cardコンポーネントのprops.childrenとして渡されます。Cardコンポーネントは、このchildren propを自身のJSX内でレンダリングすることで、親コンポーネントから渡された任意の子要素を表示することができます。このように、children propを活用することで、レイアウトを定義するコンポーネントや、特定のスタイルを適用するラッパーコンポーネントなど、様々なコンテンツを柔軟に受け入れることができる再利用可能なコンポーネントを作成することができます 6

Propsのデフォルト値

子コンポーネントでPropsが渡されなかった場合に、デフォルト値を設定することができます 6。これは、コンポーネントの柔軟性を高め、親コンポーネントがPropsを渡し忘れた場合でも、コンポーネントが適切に動作するようにするために役立ちます。関数コンポーネントでは、引数の分割代入の際にデフォルト値を指定することで実現できます。

JavaScript

// 子コンポーネント
function Greeting({ name = "ゲスト" }) {
  return <p>こんにちは、{name}さん!</p>;
}

// 親コンポーネント
function ParentComponent() {
  return (
    <div>
      <Greeting name="太郎" /> {/* "こんにちは、太郎さん!" と表示される */}
      <Greeting /> {/* name propが渡されていないため、デフォルト値が使用され "こんにちは、ゲストさん!" と表示される */}
    </div>
  );
}

上記の例では、Greetingコンポーネントのname propにデフォルト値として “ゲスト” が設定されています。親コンポーネントからname propが渡された場合はその値が使用され、渡されなかった場合はデフォルト値が使用されます。このようにデフォルト値を設定することで、コンポーネントの利用者は必須のPropsを渡し忘れても、ある程度の動作を保証することができます 6

子コンポーネントから親コンポーネントへのデータ伝達(コールバック関数をPropsとして渡す)

子コンポーネントから親コンポーネントへデータを伝達するための一般的な方法は、親コンポーネントで定義したコールバック関数をPropsとして子コンポーネントに渡し、子コンポーネント内で特定のイベントが発生した際にその関数を実行するというものです 5

コールバック関数の定義とPropsとしての受け渡し

まず、親コンポーネントで、子コンポーネントからデータを受け取るための関数を定義します 5。この関数は、子コンポーネントで発生したイベントや、子コンポーネントが持つべきデータを引数として受け取ることが一般的です。

JavaScript

// 親コンポーネント
function ParentComponent() {
  const handleDataFromChild = (data) => {
    console.log("子コンポーネントからのデータ:", data);
    // 受け取ったデータを使った処理
  };

  return <ChildComponent onDataReceived={handleDataFromChild} />;
}

次に、親コンポーネントで定義したこの関数を、Propsとして子コンポーネントに渡します 5。上記の例では、handleDataFromChild関数をonDataReceivedという名前のPropsとしてChildComponentに渡しています。

JavaScript

// 子コンポーネント
function ChildComponent({ onDataReceived }) {
  const handleClick = () => {
    const message = "Hello from Child!";
    onDataReceived(message); // 親コンポーネントから渡されたコールバック関数を実行し、データを渡す
  };

  return <button onClick={handleClick}>親にメッセージを送る</button>;
}

子コンポーネントでのコールバック関数の実行とデータの受け渡し

子コンポーネント内では、親コンポーネントからPropsとして受け取ったコールバック関数を、特定のイベントが発生した際に実行します 5。上記の例では、ボタンがクリックされた際にhandleClick関数が実行され、その中でonDataReceivedというPropsとして受け取った関数が呼び出されています。この時、子コンポーネントから親コンポーネントへ渡したいデータ(ここではmessage)を、コールバック関数の引数として渡すことができます 5

このように、コールバック関数を通じて、子コンポーネントは親コンポーネントに対して、自身で発生したイベントや、保持しているデータなどを通知することができます 5。親コンポーネントは、このコールバック関数内で受け取ったデータに基づいて、自身の状態を更新したり、他の処理を実行したりすることができます。

具体的なユースケース

子コンポーネントから親コンポーネントへのデータ伝達は、様々な場面で活用されます。

  • フォームの入力値の親コンポーネントへの通知: 子コンポーネント内のフォームでユーザーが入力した値を、親コンポーネントに通知するためにコールバック関数が使用されます 5。例えば、子コンポーネントの入力フィールドの値が変更されるたびに、親コンポーネントにその値を伝え、親コンポーネントで状態を管理するといったケースです。
  • ボタンクリックなどのイベントを親コンポーネントに通知する: 子コンポーネント内のボタンがクリックされたことを親コンポーネントに知らせるために、コールバック関数が利用されます 5。これにより、親コンポーネントは子コンポーネントで発生した特定のユーザーアクションに応じて、必要な処理を実行することができます。
  • 子コンポーネントの状態の変化を親コンポーネントに通知する: 子コンポーネント内部の状態が変化した際に、その情報を親コンポーネントに伝えるためにコールバック関数が使用されることがあります 12。例えば、子コンポーネントでAPIリクエストが完了し、データが更新されたことを親コンポーネントに通知し、親コンポーネントでUIを再描画するといったケースです。

兄弟コンポーネント間での通信(Context APIの利用)

ReactのContext APIは、コンポーネントツリー全体でデータを共有するための仕組みを提供します 7。特に、深いネスト構造を持つコンポーネントツリーにおいて、Propsを何層にも渡って受け渡す(Props Drilling)問題を解決するのに役立ちます 3。兄弟関係にあるコンポーネント間で直接Propsをやり取りすることはできませんが、Context APIを利用することで、共通の祖先コンポーネントを通じて間接的にデータを共有することが可能になります。

Context APIの基本的な概念

Context APIは、グローバルな状態管理ツールではありませんが、アプリケーション内の特定の部分木において、多くのコンポーネントで共通して利用したいデータを共有するのに適しています.22 例えば、アプリケーション全体で適用したいテーマ(ライトモード/ダークモード)や、ユーザーの認証情報などをContextとして提供することで、深い階層にあるコンポーネントでも簡単にこれらの情報にアクセスできるようになります。

Contextの作成、ProviderとConsumer

Context APIを利用するには、まずReact.createContext()を用いてContextオブジェクトを作成します 8。この関数は、ProviderコンポーネントとConsumerコンポーネント(またはuseContextフック)を返します。

Providerコンポーネントは、Contextの値を供給する役割を持ちます 8。<MyContext.Provider value={…}>のように、value propに共有したいデータを指定して、Contextを利用したいコンポーネントツリーを囲みます 21。このProviderコンポーネントの下にあるすべてのコンポーネントは、Contextの値にアクセスできるようになります。

Contextの値を利用する方法としては、Consumerコンポーネントを使う方法と、useContext()フックを使う方法があります 8。Consumerコンポーネントは、レンダープロップパターンを用いてContextの値を受け取ります。一方、useContext()フックは、関数コンポーネント内でより簡単にContextの値にアクセスするための方法です 16

useContextフックの利用

useContextフックは、関数コンポーネント内でContextの値にアクセスするための最も簡単な方法の一つです 16。まず、reactからuseContextフックをインポートし、アクセスしたいContextオブジェクトを引数として渡すだけで、そのContextの現在の値を取得できます。

JavaScript

import React, { createContext, useContext, useState } from 'react';

const ThemeContext = createContext('light'); // デフォルト値

function ParentComponent() {
  const = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <SiblingComponentA />
      <SiblingComponentB />
    </ThemeContext.Provider>
  );
}

function SiblingComponentA() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <div>
      現在のテーマ: {theme}
      <button onClick={() => setTheme(theme === 'light'? 'dark' : 'light')}>
        テーマを変更
      </button>
    </div>
  );
}

function SiblingComponentB() {
  const { theme } = useContext(ThemeContext);
  return (
    <div>
      現在のテーマ: {theme}
    </div>
  );
}

上記の例では、ParentComponentでThemeContextを作成し、そのProviderで2つの兄弟コンポーネント(SiblingComponentAとSiblingComponentB)を囲んでいます。SiblingComponentAとSiblingComponentBの両方でuseContext(ThemeContext)を呼び出すことで、Contextのvalueとして渡されたthemeとsetThemeにアクセスし、利用することができます。このように、兄弟コンポーネントは、共通の祖先コンポーネントが提供するContextを通じて、直接Propsを受け渡すことなくデータを共有することができます.5 また、複数のContextを同時に利用することも可能です 27

Context APIの利点と欠点

Context APIの主な利点は、Props Drillingを回避できることです 7。深いネスト構造を持つコンポーネントツリーでも、必要なコンポーネントだけがContextの値にアクセスできるため、中間コンポーネントでの不要なPropsの受け渡しが不要になります。また、Contextを利用することで、コンポーネントの再利用性が向上する可能性もあります 22。実装が比較的シンプルであることも利点の一つです 22

一方で、欠点としては、Contextの値が変更されると、それを消費している全てのコンポーネントが再レンダリングされる可能性がある点が挙げられます 22。また、過度な利用は、どのコンポーネントがどのContextに依存しているのかを把握しにくくし、コードの可読性を損なう可能性もあります 22。そのため、Context APIは、アプリケーション全体で頻繁に利用される可能性のある、比較的静的なデータ(テーマ、ロケール、認証情報など)の共有に適していると言えます。

兄弟コンポーネント間での通信(Reduxなどのグローバルな状態管理ライブラリの利用)

大規模なアプリケーションや、より複雑な状態管理が必要な場合には、ReduxやZustandなどのグローバルな状態管理ライブラリの利用が有効な選択肢となります 7。これらのライブラリは、アプリケーション全体の状態を一元的に管理し、どのコンポーネントからでもその状態にアクセスしたり、更新したりするための仕組みを提供します。兄弟コンポーネント間だけでなく、親子関係のない離れたコンポーネント間での通信も容易に行うことができます。

Reduxの基本的な概念(Store, Action, Reducer)

Reduxは、アプリケーションの状態を単一のStoreというオブジェクトの中に保持します 7。状態を変更するためには、Actionという「何が起こったか」を記述したオブジェクトを発行(dispatch)します 7。そして、Reducerという関数が、受け取ったActionに基づいてStoreの状態をどのように更新するかを決定します 7

コンポーネントから状態を変更したい場合、dispatch関数を使ってActionを発行します 7。Storeの状態が更新されると、その状態を監視しているコンポーネントは自動的に再レンダリングされます。コンポーネントがStoreの状態にアクセスするためには、Reduxが提供するconnect関数(クラスコンポーネントの場合)やuseSelectorフック(関数コンポーネントの場合)などを使用します 17

Zustandなどの他のライブラリの紹介

Redux以外にも、Zustandのように、より軽量でボイラープレートが少ない状態管理ライブラリも存在します 16。Zustandは、ReduxのようなActionやReducerといった概念を持ちませんが、よりシンプルで直感的なAPIを提供し、高速な状態管理を実現します。アプリケーションの規模や複雑さ、そしてチームの経験などに応じて、最適なライブラリを選択することが重要です 30

兄弟コンポーネント間での状態共有の例

グローバルな状態管理ライブラリを利用することで、兄弟コンポーネントは直接的なPropsの受け渡しなしに、状態を共有し、互いに影響を与えることができます 7。一方の兄弟コンポーネントがActionを発行し、Reducerを通じてStoreの状態を更新すると、もう一方の兄弟コンポーネントはその状態の変化を検知し、必要に応じて再レンダリングを行います。

JavaScript

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';

// Action
const increment = () => {
  return {
    type: 'INCREMENT'
  };
};

// Reducer
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    default:
      return state;
  }
};

// Store (Redux Toolkitを使用する場合)
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
  reducer: counterReducer
});

// 兄弟コンポーネントA
function SiblingComponentA() {
  const count = useSelector((state) => state.count);
  return (
    <div>
      カウント: {count}
    </div>
  );
}

// 兄弟コンポーネントB
function SiblingComponentB() {
  const dispatch = useDispatch();
  return (
    <button onClick={() => dispatch(increment())}>
      カウントを増やす
    </button>
  );
}

// 親コンポーネント (Providerで囲む)
import { Provider } from 'react-redux';

function ParentComponent() {
  return (
    <Provider store={store}>
      <SiblingComponentA />
      <SiblingComponentB />
    </Provider>
  );
}

上記の例では、SiblingComponentBのボタンがクリックされると、increment Actionが発行され、ReducerによってStoreのcountの状態が更新されます。SiblingComponentAはuseSelectorフックを通じてStoreのcountの状態を監視しているため、状態が更新されると自動的に再レンダリングされ、新しいカウントが表示されます。このように、グローバルな状態管理ライブラリは、コンポーネント間の疎結合な通信を実現し、アプリケーション全体の状態をより組織的に管理することを可能にします.7

グローバル状態管理ライブラリの選択

グローバルな状態管理ライブラリを選択する際には、アプリケーションの規模や複雑さ、そしてチームの経験などを考慮する必要があります 30。Reduxは、大規模で複雑なアプリケーションに適しており、豊富なエコシステムと強力なデバッグツールを提供しますが、設定や記述に必要なコード量が多いという特徴があります。一方、Zustandは、より軽量でシンプルなAPIを提供するため、比較的小規模なアプリケーションや、Reduxのボイラープレートに煩わしさを感じている場合に適しています 16。Context APIは、より小規模なアプリケーションや、特定の範囲での状態共有に適しています 22。それぞれのライブラリのトレードオフを理解し、プロジェクトの要件に最も適したものを選択することが重要です 22

useRefを用いたコンポーネント間の直接的な通信

useRefフックは、通常、DOM要素への直接的なアクセスや、レンダリングに関わらない値をコンポーネントの再レンダリング間で保持するために使用されますが、React.forwardRefと組み合わせることで、カスタムコンポーネントのインスタンスへの参照を取得し、直接的な通信を行うことも可能です 4

useRefの基本的な使い方

useRefは、関数コンポーネント内で使用できるHookの一つで、レンダリングに関わらない値を保持するために使用されます 4。useRef()を呼び出すと、.currentプロパティを持つオブジェクトが返されます。この.currentプロパティには、初期値として渡された値が設定され、コンポーネントのライフサイクルを通じて保持されます 4。useRefの最も一般的な用途の一つは、DOM要素への参照を取得することです 4

DOM要素へのアクセス

useRef()で作成したrefオブジェクトを、JSX要素のref属性に渡すことで、そのDOM要素への参照をref.currentを通じて取得することができます 4。これにより、例えば、入力フィールドにフォーカスを当てたり、要素のスクロール位置を操作したりといった、命令的なDOM操作を行うことが可能になります 4

JavaScript

import React, { useRef } from 'react';

function MyInputComponent() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={focusInput}>入力にフォーカス</button>
    </div>
  );
}

カスタムコンポーネントへのアクセスとforwardRef

デフォルトでは、親コンポーネントから子コンポーネントへのrefの受け渡しは行われません 36。もし親コンポーネントが子コンポーネントのカスタムインスタンスにアクセスする必要がある場合、子コンポーネントはReact.forwardRefを使用して、親から渡されたrefを受け取る必要があります 20。forwardRefは、レンダー関数を引数として取り、そのレンダー関数はPropsとrefの2つの引数を受け取ります。このレンダー関数内で、受け取ったrefを子コンポーネント内のDOM要素または別のカスタムコンポーネントに渡すことができます。

useImperativeHandleの利用

useImperativeHandleは、forwardRefと組み合わせて使用することで、親コンポーネントからrefを通じてアクセスできる子コンポーネントのインスタンスのプロパティやメソッドを制御することができます 20。useImperativeHandleフックは、2つの引数を取り、最初の引数はforwardRefから受け取ったref、2番目の引数は、親コンポーネントに公開したいメソッドやプロパティを含むオブジェクトを返す関数です。

JavaScript

import React, { useRef, forwardRef, useImperativeHandle } from 'react';

const ChildComponent = forwardRef((props, ref) => {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  useImperativeHandle(ref, () => ({
    focus: focusInput // 親コンポーネントから 'focus' という名前で focusInput 関数を呼び出せるようにする
  }));

  return <input type="text" ref={inputRef} />;
});

function ParentComponent() {
  const childRef = useRef(null);

  const handleClick = () => {
    childRef.current.focus(); // 子コンポーネントの focusInput 関数を呼び出す
  };

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={handleClick}>子コンポーネントの入力にフォーカス</button>
    </div>
  );
}

上記の例では、ParentComponentはChildComponentへのrefを作成し、それをchildRefに保持しています。ChildComponentはforwardRefを使って親からrefを受け取り、useImperativeHandleを使って、親コンポーネントからchildRef.current.focus()を呼び出すことで、内部のfocusInput関数を実行できるようにしています。

直接的な通信のユースケースと注意点

useRefを用いた直接的な通信は、アニメーションのトリガー、フォーカスの設定、サードパーティライブラリとの連携など、命令的な操作が必要な場合に有効です 20。しかし、過度な使用はコンポーネント間の結合度を高め、データの流れを分かりにくくする可能性があるため、慎重に利用する必要があります 20。Reactのデータフローの原則に従い、可能な限りPropsとコールバック関数、あるいは状態管理ライブラリを用いた通信を優先するべきです。useRefによる直接的な操作は、どうしても必要な場合に限定的に使用することが推奨されます 20

さまざまなコンポーネント通信のパターンとそれぞれのユースケース

Reactアプリケーションの開発においては、様々なコンポーネント通信のパターンが存在し、それぞれのパターンは特定のユースケースに適しています。

Props Drillingとその対策

Props Drillingは、深いネスト構造を持つコンポーネントツリーにおいて、あるコンポーネントが必要とするPropsが、直接の親子関係にない上位のコンポーネントから、必要のない中間コンポーネントを経由して何層にも渡って受け渡される現象を指します 3。これは、コードの可読性や保守性を低下させる原因となります。Props Drillingへの対策としては、Context APIやグローバル状態管理ライブラリの利用が挙げられます 7。これらの方法を用いることで、深い階層にあるコンポーネントでも、必要なデータに直接アクセスできるようになります。また、コンポーネント合成(children propの活用)も、Props Drillingを避けるための有効な手段です 6

Container/Presentationalパターン

Container/Presentationalパターンは、表示ロジック(Presentational Component)とビジネスロジック(Container Component)を分離するためのデザインパターンです 28。Containerコンポーネントは、データの取得や状態の管理などのビジネスロジックを担当し、Presentationalコンポーネントは、受け取ったPropsに基づいてUIをレンダリングすることに特化します 28。このパターンを適用することで、コンポーネントの責務が明確になり、再利用性やテスト容易性が向上します 28

その他の一般的なパターン

他にも、Render Propsパターン、Higher-Order Components (HOC)パターン、Custom Hooksパターンなど、様々なコンポーネント通信や再利用のためのパターンが存在します 9。Render Propsパターンは、Propsとして関数を渡し、その関数を通じてレンダリングロジックを共有するパターンです 9。HOCパターンは、コンポーネントを受け取り、拡張された新しいコンポーネントを返す関数を利用して、共通の機能を複数のコンポーネントに追加するパターンです 9。Custom Hooksパターンは、状態を持つロジックを関数として抽出し、複数のコンポーネントで再利用できるようにするパターンです 16

各パターンの適用例

  • Context APIを用いたテーマの切り替え: アプリケーション全体のテーマ(ライトモード/ダークモード)をContext APIで管理し、複数のコンポーネントでテーマの状態を共有し、切り替えを行うことができます 3
  • Reduxを用いたユーザー認証状態の管理: ユーザーがログインしているかどうかといった認証状態をReduxなどのグローバル状態管理ライブラリで管理し、アプリケーション内の様々なコンポーネントからその状態にアクセスし、UIを制御することができます 7
  • useRefを用いた入力要素へのフォーカス: 特定の条件下で、useRefを用いて取得した入力要素の参照に対してfocus()メソッドを呼び出すことで、プログラム的にフォーカスを移動させることができます 4
パターン説明一般的なユースケース
Props DrillingPropsを複数の中間コンポーネントを通して受け渡す小規模〜中規模アプリケーションでの単純なデータ受け渡し
Context API明示的なPropsの受け渡しなしに、コンポーネントツリーの一部でデータを共有するテーマ設定、ユーザー認証情報、ロケール設定
グローバル状態管理アプリケーション全体の状態を一元管理する大規模で複雑なアプリケーション、多くの状態依存性、ユーザー認証、データキャッシュ
Container/Presentationalデータ取得/ロジックとUIレンダリングを分離するコードの組織化、再利用性、テスト容易性の向上
Render Props関数Propsを使ってレンダリングロジックを共有するカスタマイズ可能なレンダリング動作を持つ再利用可能なUIコンポーネントの作成
Higher-Order Components (HOC)共通の機能でコンポーネントを拡張するロギング、認証チェック、その他の横断的な関心を複数のコンポーネントに追加する
Custom Hooks状態を持つロジックをカプセル化し、複数のコンポーネントで再利用するフォームの状態管理、データ取得ロジック、その他の再利用可能な振る舞い
useRef (with forwardRef)DOM要素またはコンポーネントインスタンスへの直接アクセスを取得するアニメーションのトリガー、要素へのフォーカス、サードパーティライブラリとの統合、子コンポーネントの振る舞いの制御

Reactのコンポーネント通信におけるベストプラクティスと注意点

Reactで効果的なコンポーネント通信を行うためには、いくつかのベストプラクティスと注意点があります。

明確なデータの流れを保つ

コンポーネント間のデータの流れを常に明確に保つことが重要です 7。Reactの単方向データフローの原則に従い 6、Propsは親から子へ、イベントは子から親へという基本的なパターンを守るように心がけましょう 3

不要なPropsの受け渡しを避ける

必要のないデータをPropsとして子コンポーネントに渡すことは避けるべきです 7。本当に必要なデータだけをPropsとして渡し、Props Drillingが深すぎる場合は、Context APIや状態管理ライブラリの利用を検討しましょう 7

適切な通信方法の選択

コンポーネント間の通信の目的や関係性に応じて、適切な方法を選択することが重要です 7。親子間の単純なデータ伝達にはPropsとコールバック関数を利用し、兄弟間や深いネストのコンポーネント間でのデータ共有にはContext APIや状態管理ライブラリを利用するのが一般的です 7。useRefは、直接的なDOM操作やコンポーネントへのアクセスが必要な場合に限定的に使用するべきです 4

状態管理の原則

状態は、できるだけ上位のコンポーネントで管理するようにしましょう 7。関連する状態はまとめて管理することで、アプリケーションの状態をより把握しやすく、管理しやすくすることができます 38

パフォーマンスに関する考慮事項

不要な再レンダリングを避けるために、React.memoやshouldComponentUpdate(クラスコンポーネントの場合)を利用することを検討しましょう 24。Context APIのProviderのvalue propが頻繁に変わる場合、それを消費している全てのコンポーネントが再レンダリングされる可能性があるため注意が必要です 22

よくある間違いとその回避策

  • Propsの直接的な変更: 子コンポーネント内でPropsを直接変更しようとすることは避けるべきです 1
  • 深いProps Drilling: Props Drillingが深くなりすぎると、コードの可読性や保守性が低下するため、Context APIや状態管理ライブラリの利用を検討しましょう 3
  • 不適切な状態管理: 状態を持つべき場所を誤ると、コンポーネント間の連携が複雑になることがあります。状態はできるだけ上位の、関連するコンポーネントで管理するようにしましょう 3
  • useEffectの誤用: useEffectは、副作用を扱うためのHookであり、コンポーネントの状態に基づいて値を計算するような場合には、直接状態を更新するか、useMemoなどのHookを利用する方が適切です 24
  • 過度な第三者ライブラリの利用: 多くのライブラリを導入すると、アプリケーションの肥大化やメンテナンスの複雑化につながる可能性があります。必要な機能を見極め、慎重に選択しましょう 25

結論

Reactにおけるコンポーネント間の通信は、動的でインタラクティブなアプリケーションを構築するための基盤となる重要な概念です。本稿では、Props、コールバック関数、Context API、グローバル状態管理ライブラリ、そしてuseRefといった主要な通信方法について詳しく解説しました。それぞれの方法には特性と適したユースケースがあり、アプリケーションの要件に応じて適切な方法を選択することが重要です。効果的なコンポーネント通信を行うことで、保守性と拡張性の高いReactアプリケーションの開発が可能になります。今後もReactの進化に伴い、新たな通信パターンやベストプラクティスが登場する可能性がありますが、本稿で解説した基本的な概念と原則を理解しておくことは、React開発者として成長していく上で非常に重要となるでしょう。

引用文献

  1. Components and Props – React, 4月 7, 2025にアクセス、 https://legacy.reactjs.org/docs/components-and-props.html
  2. Your First Component – React, 4月 7, 2025にアクセス、 https://react.dev/learn/your-first-component
  3. Event-Driven Architecture for Clean React Component Communication, 4月 7, 2025にアクセス、 https://dev.to/nicolalc/event-driven-architecture-for-clean-react-component-communication-fph
  4. useRef – React, 4月 7, 2025にアクセス、 https://react.dev/reference/react/useRef
  5. React — component communication – Jeff P – Medium, 4月 7, 2025にアクセス、 https://waxlyrical.medium.com/react-component-communication-2d9cf0f5f797
  6. Passing Props to a Component – React, 4月 7, 2025にアクセス、 https://react.dev/learn/passing-props-to-a-component
  7. Tutorial: How to communicate between two React components – Front End Engineering, 4月 7, 2025にアクセス、 https://www.frontendeng.dev/blog/5-tutorial-how-to-communicate-between-two-components-in-react
  8. Passing Data Between Parent and Child Components in React — PART 1 | by Dev Balaji, 4月 7, 2025にアクセス、 https://dvmhn07.medium.com/passing-data-between-parent-and-child-components-in-react-part-b405116c470e
  9. Passing props to child components in React function components – CoreUI, 4月 7, 2025にアクセス、 https://coreui.io/blog/passing-props-to-child-components-in-react-function-components/
  10. Updating Arrays in State – React, 4月 7, 2025にアクセス、 https://react.dev/learn/updating-arrays-in-state
  11. How Do You Pass Props to `{this.props.children}`? – Sentry, 4月 7, 2025にアクセス、 https://sentry.io/answers/react-passing-props-to-children/
  12. Passing Data from Child to Parent Components in React using Callback Functions, 4月 7, 2025にアクセス、 https://rakeshgk.hashnode.dev/passing-data-from-child-to-parent-components-in-react-using-callback-functions
  13. Introduction to Child-to-Parent Communication – Stackademic, 4月 7, 2025にアクセス、 https://blog.stackademic.com/introduction-to-child-to-parent-communication-5ca17c553366
  14. Passing Data from Child to Parent Components in React | by ozhanli – Medium, 4月 7, 2025にアクセス、 https://medium.com/@ozhanli/passing-data-from-child-to-parent-components-in-react-e347ea60b1bb
  15. How to pass props from child to parent component in React – DEV Community, 4月 7, 2025にアクセス、 https://dev.to/bcostaaa01/how-to-pass-props-from-child-to-parent-component-in-react-1ci4
  16. Best Ways To Pass Data Between Components On React – Sivabharathy, 4月 7, 2025にアクセス、 https://sivabharathy.in/blog/best-ways-to-pass-data-between-components-on-react/
  17. Component Communication Patterns : Course React JS for Beginners – Cursa, 4月 7, 2025にアクセス、 https://cursa.app/en/page/components-component-communication-patterns
  18. Communicating Between Components in React – Pluralsight, 4月 7, 2025にアクセス、 https://www.pluralsight.com/resources/blog/guides/react-communicating-between-components
  19. Passing Props to a Component – React, 4月 7, 2025にアクセス、 https://react.dev/learn/passing-props-to-a-component#passing-event-handlers-as-props
  20. React Component Communication: Parent-Child and Child-Parent Interactions, 4月 7, 2025にアクセス、 https://dev.to/sandheep_kumarpatro_1c48/react-component-communication-parent-child-and-child-parent-interactions-38og
  21. Passing Data Deeply with Context – React, 4月 7, 2025にアクセス、 https://react.dev/learn/passing-data-deeply-with-context
  22. React : Communication between components using context – Front End Engineering, 4月 7, 2025にアクセス、 https://www.frontendeng.dev/blog/51-react-inter-component-communication-through-context
  23. Sharing data between siblings components using Context in React with Hooks, 4月 7, 2025にアクセス、 https://dev.to/jcbonassin/sharing-data-between-siblings-components-using-context-in-react-with-hooks-2b8i
  24. React Best Practices – A 10-Point Guide – UXPin, 4月 7, 2025にアクセス、 https://www.uxpin.com/studio/blog/react-best-practices/
  25. ReactJS Development: 7 common mistakes developers must avoid – Kellton, 4月 7, 2025にアクセス、 https://www.kellton.com/kellton-tech-blog/7-reactjs-development-mistakes-to-avoid
  26. Prop Drilling – Kent C. Dodds, 4月 7, 2025にアクセス、 https://kentcdodds.com/blog/prop-drilling
  27. In React, Is there a way to communicate between two components in both sides (duplex) using React Context API? – Stack Overflow, 4月 7, 2025にアクセス、 https://stackoverflow.com/questions/59353098/in-react-is-there-a-way-to-communicate-between-two-components-in-both-sides-du
  28. A Dive into React Design Patterns | by Dzmitry Ihnatovich | Medium, 4月 7, 2025にアクセス、 https://medium.com/@ignatovich.dm/a-dive-into-react-design-patterns-76dcd62ccd19
  29. context api react hooks tutorial | pass data between sibling components in react – YouTube, 4月 7, 2025にアクセス、 https://www.youtube.com/watch?v=Dl7Cy23C_mE
  30. State Management: Comparing Redux Toolkit, Zustand, and React Context, 4月 7, 2025にアクセス、 https://prakashinfotech.com/state-management-comparing-redux-toolkit-zustand-and-react-context
  31. Sharing Variables Between Components in ReactJS – Squash.io, 4月 7, 2025にアクセス、 https://www.squash.io/sharing-variables-between-components-in-reactjs/
  32. How can I communicate between two react sibling component directly without using parent component? – Stack Overflow, 4月 7, 2025にアクセス、 https://stackoverflow.com/questions/55074271/how-can-i-communicate-between-two-react-sibling-component-directly-without-using
  33. React useRef() – A complete guide – Hygraph, 4月 7, 2025にアクセス、 https://hygraph.com/blog/react-useref-a-complete-guide
  34. Understanding the useRef Hook in React – Kinsta®, 4月 7, 2025にアクセス、 https://kinsta.com/knowledgebase/react-useref/
  35. What is useRef in React? How to use it? – SheCodes, 4月 7, 2025にアクセス、 https://www.shecodes.io/athena/89879-what-is-useref-in-react-how-to-use-it
  36. Manipulating the DOM with Refs – React, 4月 7, 2025にアクセス、 https://react.dev/learn/manipulating-the-dom-with-refs
  37. 1月 1, 1970にアクセス、 https://www.robinwieruch.de/react-component-communication/
  38. React Design Patterns – Refine dev, 4月 7, 2025にアクセス、 https://refine.dev/blog/react-design-patterns/
  39. What are the most common mistakes done by professional React developers? – Reddit, 4月 7, 2025にアクセス、 https://www.reddit.com/r/reactjs/comments/19fmazy/what_are_the_most_common_mistakes_done_by/
  40. Bad practices in Reactjs – Reddit, 4月 7, 2025にアクセス、 https://www.reddit.com/r/reactjs/comments/1hnc0v3/bad_practices_in_reactjs/
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次