ホームページ >ウェブフロントエンド >jsチュートリアル >クリーンな React コンポーネント通信のためのイベント駆動型アーキテクチャ

クリーンな React コンポーネント通信のためのイベント駆動型アーキテクチャ

Barbara Streisand
Barbara Streisandオリジナル
2024-12-06 15:26:13341ブラウズ

React アプリケーションでのプロップのドリルリングとコールバック チェーンの終わりのないもつれにうんざりしていませんか?深くネストされたコンポーネント間の状態と通信の管理は、スパゲッティ コードと格闘するような感じですか?

イベント駆動型アーキテクチャは、コンポーネントの対話を簡素化し、複雑さを軽減し、アプリをより保守しやすくします。この記事では、カスタム useEvent フックを使用してコンポーネントを分離し、React アプリ全体の通信を改善する方法を説明します。

順を追って説明しましょう。まずは


問題: 小道具のドリルリングとコールバック チェーン

現代のアプリケーション開発では、コンポーネント間の状態と通信の管理がすぐに面倒になることがあります。これは、プロップ ドリル (複数レベルのネストされたコンポーネントを介してデータを渡す必要がある場合) や コールバック チェーン を含むシナリオに特に当てはまります。これにより、ロジックが複雑になり、コードの実行が困難になる可能性があります。メンテナンスまたはデバッグ。

これらの課題により、緊密に結合されたコンポーネントが作成されることが多く、柔軟性が低下し、アプリケーション内でデータがどのように流れるかを追跡しようとする開発者にとって認知的負荷が増大します。より良いアプローチがなければ、この複雑さによって開発が大幅に遅くなり、脆弱なコードベースにつながる可能性があります。

従来のフロー: プロップはダウン、コールバックはアップ

典型的な React アプリケーションでは、親コンポーネントが子に props を渡し、子はコールバックをトリガーすることで親に通信を返します。これは浅いコンポーネント ツリーではうまく機能しますが、階層が深くなると、状況が複雑になり始めます。

プロップドリル: データは、最も深いコンポーネントでのみ必要な場合でも、複数のレベルのコンポーネントを介して手動で受け渡す必要があります。

コールバック チェーン: 同様に、子コンポーネントはイベント ハンドラーをツリーの上に転送する必要があり、密結合で保守が難しい構造を作成します。

よくある問題: コールバックの複雑さ

次のシナリオを例に考えてみましょう:

  • 親は 子 A にプロップを渡します。
  • そこから、プロパティは GrandChildren A/B にドリルダウンされ、最終的には SubChildren N にドリルダウンされます。
  • SubChildren N が親にイベントを通知する必要がある場合、コールバック がトリガーされ、各中間コンポーネント を経由して戻ります。

アプリケーションが大きくなるにつれて、この設定の管理は難しくなります。中間コンポーネントは多くの場合、プロップとコールバックを転送する仲介者としてのみ機能するため、コードが肥大化して保守性が低下します。

Event-Driven Architecture for Clean React Component Communication

プロップドリルに対処するために、データ共有を合理化するために、グローバル状態管理ライブラリ (例: Zustand) などのソリューションに頼ることがよくあります。しかし、コールバックの管理についてはどうすればよいでしょうか?

ここで、イベント駆動型のアプローチが変革をもたらす可能性があります。コンポーネントを分離し、イベントに依存してインタラクションを処理することで、コールバック管理を大幅に簡素化できます。このアプローチがどのように機能するかを見てみましょう。


解決策: イベント駆動型アプローチを導入する

Event-Driven Architecture for Clean React Component Communication

ツリー上の通信に直接コールバックに依存するのではなく、イベント駆動型アーキテクチャによりコンポーネントが分離され、通信が集中化されます。仕組みは次のとおりです:

イベントのディスパッチ

SubChildren N がイベント (例: onMyEvent) をトリガーするとき、親のコールバックは直接呼び出されません。
代わりに、集中イベント ハンドラーによって処理されるイベントをディスパッチします

集中処理

イベント ハンドラーは、ディスパッチされたイベントをリッスンして処理します。
必要に応じて、親 (またはその他の関係するコンポーネント) に通知したり、追加のアクションをトリガーしたりできます。

プロップは下向きのまま

プロパティは引き続き階層の下に渡され、コンポーネントが機能するために必要なデータを確実に受け取ります。

これは、zustand、redux などの集中状態管理ツールで解決できますが、この記事では取り上げません。


実装

しかし、このアーキテクチャをどのように実装すればよいでしょうか?

useイベントフック

useEvent というカスタム フックを作成しましょう。このフックは、イベント サブスクリプションを処理し、ターゲット イベントをトリガーするディスパッチ関数を返す役割を果たします。

typescript を使用しているため、カスタム イベントを作成するにはウィンドウのイベント インターフェイスを拡張する必要があります。

interface AppEvent<PayloadType = unknown> extends Event {
  detail: PayloadType;
}

export const useEvent = <PayloadType = unknown>(
  eventName: keyof CustomWindowEventMap,
  callback?: Dispatch<PayloadType> | VoidFunction
) => {
  ...
};

そうすることで、カスタム イベント マップを定義し、カスタム パラメーターを渡すことができます。

interface AppEvent<PayloadType = unknown> extends Event {
  detail: PayloadType;
}

export interface CustomWindowEventMap extends WindowEventMap {
  /* Custom Event */
  onMyEvent: AppEvent<string>; // an event with a string payload
}

export const useEvent = <PayloadType = unknown>(
  eventName: keyof CustomWindowEventMap,
  callback?: Dispatch<PayloadType> | VoidFunction
) => {
  ...
};

必要なインターフェイスを定義したので、最終的なフック コードを見てみましょう

import { useCallback, useEffect, type Dispatch } from "react";

interface AppEvent<PayloadType = unknown> extends Event {
  detail: PayloadType;
}

export interface CustomWindowEventMap extends WindowEventMap {
  /* Custom Event */
  onMyEvent: AppEvent<string>;
}

export const useEvent = <PayloadType = unknown>(
  eventName: keyof CustomWindowEventMap,
  callback?: Dispatch<PayloadType> | VoidFunction
) => {
  useEffect(() => {
    if (!callback) {
      return;
    }

    const listener = ((event: AppEvent<PayloadType>) => {
      callback(event.detail); // Use `event.detail` for custom payloads
    }) as EventListener;

    window.addEventListener(eventName, listener);
    return () => {
      window.removeEventListener(eventName, listener);
    };
  }, [callback, eventName]);

  const dispatch = useCallback(
    (detail: PayloadType) => {
      const event = new CustomEvent(eventName, { detail });
      window.dispatchEvent(event);
    },
    [eventName]
  );

  // Return a function to dispatch the event
  return { dispatch };
};

useEvent フックは、カスタム ウィンドウ イベントをサブスクライブしてディスパッチするためのカスタム React フックです。これにより、カスタム イベントをリッスンし、特定のペイロードでそれらをトリガーすることができます。

ここで行っていることは非常に単純です。標準のイベント管理システムを使用し、カスタム イベントに対応するためにそれを拡張しています。

パラメータ:

  • eventName (文字列): リッスンするイベントの名前。
  • callback (オプション): イベントがトリガーされたときに呼び出す関数で、引数としてペイロードを受け取ります。

特徴:

  • イベント リスナー: 指定されたイベントをリッスンし、イベントの詳細 (カスタム ペイロード) を使用して提供されたコールバックを呼び出します。
  • イベントのディスパッチ: フックは、カスタム ペイロードでイベントをトリガーするディスパッチ関数を提供します。

例:

interface AppEvent<PayloadType = unknown> extends Event {
  detail: PayloadType;
}

export const useEvent = <PayloadType = unknown>(
  eventName: keyof CustomWindowEventMap,
  callback?: Dispatch<PayloadType> | VoidFunction
) => {
  ...
};

わかりました。でも、どうでしょうか?

現実世界の例?

この StackBlitz をチェックしてください (ロードされない場合は、ここで確認してください)

この簡単な例は useEvent フックの目的を示しています。基本的に本体のボタンは、サイドバー、ヘッダー、およびフッター コンポーネントからインターセプトされたイベントを送出し、それに応じて更新されます。

これにより、多くのコンポーネントにコールバックを伝播することなく、原因/結果反応を定義できるようになります。


useEvent の実世界の使用例

ここでは、useEvent フックが通信を簡素化し、React アプリケーション内のコンポーネントを分離できる、実際の使用例をいくつか示します。


1. 通知システム

通知システムでは、グローバルなコミュニケーションが必要になることがよくあります。

  • シナリオ:

    • API 呼び出しが成功すると、アプリ全体に「成功」​​通知が表示される必要があります。
    • ヘッダー内の「通知バッジ」などのコンポーネントも更新する必要があります。
  • 解決策: useEvent フックを使用して、通知の詳細を含む onNotification イベントを送出します。 NoticeBanner や Header などのコンポーネントは、このイベントをリッスンして個別に更新できます。

2. テーマの切り替え

ユーザーがテーマ (ライト/ダーク モードなど) を切り替えると、複数のコンポーネントが応答する必要がある場合があります。

  • シナリオ:

    • ThemeToggle コンポーネントはカスタムの onThemeChange イベントを送出します。
    • サイドバーやヘッダーなどのコンポーネントはこのイベントをリッスンし、それに応じてスタイルを更新します。
  • 利点: コンポーネント ツリー全体で props を介してテーマの状態やコールバック関数を渡す必要がありません。

3. グローバルキーバインド

「Ctrl S」を押して下書きを保存したり、「Escape」を押してモーダルを閉じるなどのグローバル ショートカットを実装します。

  • シナリオ:
    • グローバル キーダウン リスナーは、押されたキーの詳細を含む onShortcutPressed イベントを送出します。
    • モーダル コンポーネントまたはその他の UI 要素は、キー イベントの転送を親コンポーネントに依存せずに、特定のショートカットに応答します。

4. リアルタイム更新

チャット アプリやライブ ダッシュボードなどのアプリケーションでは、リアルタイムの更新に反応するために複数のコンポーネントが必要です。

  • シナリオ:
    • WebSocket 接続は、新しいデータが到着すると onNewMessage または onDataUpdate イベントを送出します。
    • チャット ウィンドウ、通知、未読メッセージ カウンターなどのコンポーネントは、独立して更新を処理できます。

5. コンポーネント間のフォーム検証

複数のセクションを持つ複雑なフォームの場合、検証イベントを一元化できます。

  • シナリオ:
    • ユーザーがフィールドに入力すると、フォーム コンポーネントは onFormValidate イベントを送出します。
    • 概要コンポーネントは、フォーム ロジックと密接に連携することなく、これらのイベントをリッスンして検証エラーを表示します。

6. 分析の追跡

ユーザーの操作 (ボタンのクリック、ナビゲーション イベントなど) を追跡し、分析サービスに送信します。

  • シナリオ:
    • 関連する詳細 (クリックされたボタンのラベルなど) を含む onUserInteraction イベントを送信します。
    • 中央分析ハンドラーはこれらのイベントをリッスンし、分析 API に送信します。

7. コラボレーションツール

共有ホワイトボードやドキュメントエディターなどの共同作業ツールの場合、イベントはマルチユーザーの対話を管理できます。

  • シナリオ:
    • ユーザーがオブジェクトを描画、入力、または移動するたびに onUserAction イベントを送出します。
    • 他のクライアントと UI コンポーネントはこれらのイベントをリッスンして、変更をリアルタイムで反映します。

これらのシナリオで useEvent フックを活用すると、深くネストされた props やコールバック チェーンに依存せずに、モジュール式で保守可能でスケーラブルなアプリケーションを作成できます。


結論

イベントは、複雑さを軽減し、モジュール性を向上させることで、React アプリケーションの構築方法を変革します。小規模から始めて、通信を分離することでメリットが得られるアプリ内のコンポーネントをいくつか特定し、useEvent フックを実装します。

このアプローチを使用すると、コードが簡素化されるだけでなく、将来の保守と拡張も容易になります。

イベントを使用する理由
イベントは、不要な依存関係や複雑なコールバック チェーンを導入することなく、アプリケーション内の他の場所で発生した何かにコンポーネントが反応する必要がある場合に威力を発揮します。このアプローチにより、認知負荷が軽減され、密結合コンポーネントの落とし穴が回避されます。

私のおすすめ
コンポーネント間の通信にはイベントを使用します。コンポーネント ツリー内の位置に関係なく、あるコンポーネントがアクションや状態の変化を他のコンポーネントに通知する必要がある場合に使用します。
コンポーネント内通信、特に密接に関連しているコンポーネントや直接接続されているコンポーネントの通信にイベントを使用することは避けてください。これらのシナリオでは、props、state、context などの React の組み込みメカニズムを利用します。

バランスの取れたアプローチ
イベントは強力ですが、使いすぎると混乱が生じる可能性があります。疎結合コンポーネント間の通信を簡素化するためにこれらを慎重に使用してください。ただし、ローカル インタラクションを管理するための React の標準ツールをそれらに置き換えさせないでください。

以上がクリーンな React コンポーネント通信のためのイベント駆動型アーキテクチャの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。