ReactでEventEmitterを実装する方法
普段Reactを利用するプロジェクトで、コンポーネント間の効率的な通信を実現するためにどのような対応を行っているでしょうか。Reactでは主にpropsや状態管理ソリューション(Redux、Zustandなど)を使ってコンポーネント間で通信を行います。ただし、2つのコンポーネントが直接接続されていない場合、イベントエミッターを使用することが効果的な解決策となります。「データのやり取りを効率化し、作業時間を短縮できる方法」という観点で、仕組みや実装手順を解説します。
この記事の目次
コミュニケーションの問題
通常、Reactでは親コンポーネントが子コンポーネントにプロパティを渡して通信します。しかし、親子レベルが深くなると、親コンポーネントで子コンポーネントのアクションを追跡するのが難しくなる場合があります。
const [data, setData] = useState({value1: "value 1", value: "value 2"});
<Parent>//Parent.tsx
<Child data={data} onChange={setData} />//Child.tsx
</Parent>
このような複雑な構造では、親コンポーネントから全ての更新ロジックを管理するのが困難です。
<Child>//Child.tsx
<SubChild data={props.data.value1} onChange={props.onChange({...props.data, value1: "new value 1"})} />//SubChild.tsx
<SubChild2 data={props.data.value2} onChange={props.onChange({...props.data, value2: "new value 2"})} />//SubChild2.tsx
</Child>
イベント・エミッターとは
イベントエミッターは、propsやstateを介さずにコンポーネント間でデータをやり取りする仕組みです。Node.jsのEventEmitterモジュールや、mitt
ライブラリを使って実装できます。
Reactでイベント・エミッターを使うとき
良い使い方:
・深くネストされたコンポーネント間で、プロップ・ドリリングなしでコミュニケーション可能
・グローバルステート(Redux、Context)を持たないコンポーネント間のデータ共有
・通知のようなグローバルイベントの処理
あまり向いていない:
・アプリ全体の状態を管理する(代わりにRedux、Zustand、Contextを使う)
・頻繁な更新(Reactの状態管理の方が効率的)
イベント・エミッターの仕組み
イベント・エミッターはパブリッシュ・サブスクライブ(pub-sub)パターンに従う:
1.イベントリスナーを登録(サブスクライブ)する
2.任意のデータでイベントを発信(公開)する
3.必要に応じてイベントリスナーを削除する
実施方法
EventEmitterは Node.js のモジュールで、インストールすれば使用可能です。今回は NextJS (Reactフレームワーク)を使います。
以下は、コンポーネントの関係です。データは子コンポーネントに受け渡され、変更があると子コンポーネントはイベントエミッターで親コンポーネントに通知します。
Parent
|
----Child
|
----SubChild
|
----SubChild2
イベント・エミッターの作成
//lib/eventEmitter.ts
import { EventEmitter } from "events";
const eventEmitter = new EventEmitter();
export default eventEmitter;
イベントの購読/購読解除
イベント・リスナーの購読にはon()を使い、イベント・リスナーの削除にはoff()を使う。
//components/Parent.tsx
"use client";
import eventEmitter from "@/lib/eventEmitter";
import { FC, useEffect, useState } from "react";
import { Child } from "./Child";
export const Parent: FC = () => {
const [data, setData] = useState({
value1: "サブ子",
value2: "サブ子2",
});
useEffect(() => {
const onValueChange = (value: string) => {
setData((prev) => ({...prev, value1: value}));
};
const onValueChange2 = (value: string) => {
setData((prev) => ({...prev, value2: value}));
};
//イベントを購読する
eventEmitter.on("value1", onValueChange);
eventEmitter.on("value2", onValueChange2);
return () => {
//イベントの登録を解除する
eventEmitter.off("value1", onValueChange);
eventEmitter.off("value2", onValueChange2);
};
}, []);
return (
<div className="flex flex-col gap-2">
<div>子コンポーネントリスト</div>
<Child data={data} />
</div>
);
};
発光イベント
emit()関数を使用してイベントを発信します。正しいイベント名を持つサブスクライバーがこのイベントを受け取ります。
//components/SubChild2.tsx
import eventEmitter from "@/lib/eventEmitter";
import { FC } from "react";
export const SubChild2: FC<{ value: string }> = (props) => {
return (
<div className="flex flex-col gap-2">
<div>現在の値:{props.value}</div>
<button
className="p-2 rounded bg-green-500 text-white"
onClick={() => {
eventEmitter.emit("value2", "サブ子2の値が変更されました!");
}}>サブ子2を編集する</button>
</div>
);
};
結果
デフォルトでは、子コンポーネントが親からデータを表示しますが、子コンポーネントのアクションに応じて親の状態が更新されます。これにより、親から子への余分なロジックの伝播を省くことができます。
子コンポーネント2の変更ボタンをクリックすると「value2」が更新されました。
このテクニックを使えば、親コンポーネントから子コンポーネントにアクションを渡す必要がなくなります。
まとめ
イベントエミッターは設定がシンプルで、特に複雑なコンポーネント構造を持つプロジェクトで役立ちます。これを活用すれば、ネストした構造をすっきり整理し、効率的にデータ通信を行うことができるためお勧めです。試してみましょう!
この情報は役に立ちましたか?
カテゴリー: