はじめに
useEffectEventってご存知ですか??
reactのドキュメントでは結構押しているっぽいhooksなのですが、まだExperimental Featureでstableのバージョンに組み込まれていないからなのかあまり記事が見つかりませんでした。なので簡単に解説します。
useEffectEventとは
useEffectEventはuseEffectと一緒に使います。
例を見てみましょう。
export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
connection.on('message', (msg) => {
onReceiveMessage(msg);
});
return () => connection.disconnect();
}, [roomId, serverUrl, onReceiveMessage]); // ✅ All dependencies declared
}
このコンポーネントはチャットルームに接続します。
useEffectの中で、チャットに接続、切断するクリーンアップ処理が記述されています。
さらにuseEffect内でメッセージが届いたときの処理ロジックであるonReceiveMessageを記述する必要があります。
その場合、onReceiveMessageを依存値として追加する必要があるので、コンポーネントが再レンダーされるたびにチャットが再接続されてしまうため、あまり望ましくありません。
これをuseEffectEventを使って、必要な時のみチャットルームに再接続するように修正してみましょう。
export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
const onMessage = useEffectEvent(onReceiveMessage);
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
connection.on('message', (msg) => {
onMessage(msg);
});
return () => connection.disconnect();
}, [roomId, serverUrl]); // ✅ All dependencies declared
}
エフェクトの依存配列にはroomIdとserverUrlだけになりました!
こうすることで、roomIdもしくはserverUrlが変更された時のみチャットに再接続されるようになります。
useEffectの依存値に含めないと最新のonReceiveMessageが適応されないという心配は不要です!
- onReceiveMessageが更新されても、エフェクトは再実行されない(接続は維持される)
- onMessageが呼ばれた時は最新のonReceiveMessageが使用される
もう一つの似た例を見てみましょう
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]); // ✅ All dependencies declared
こちらもチャットルームに接続する処理がuseEffect内で書かれていますが、テーマカラーも依存関係に入っているので、テーマカラーが変わるたびに、チャットルームに再接続する形になります。
roomIdが変わるとチャットが再接続されるのは期待通りの動作です。しかし、themeも依存値であるため、ダークテーマとライトテーマを切り替えることでも毎回チャットが再接続されてしまっています。これは理想的ではありません。
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]);
エフェクトは通常「リアクティブ」です。つまり、依存している値が変わると自動的に再実行されます。しかし、時にはエフェクトの中で「値が変わっても再実行してほしくない処理」を書きたい場合があります。
エフェクトイベントを使うと、この問題を解決できます。エフェクト自体はリアクティブのままで、一部の処理だけを「非リアクティブ」にできます。
コンポーネントの本体部分で宣言された props、state および変数のことをリアクティブな値 (reactive value) と呼びます。
エフェクトから非リアクティブなロジックを分離する
エフェクト内のロジックはリアクティブです。roomIdが変化することは、別の部屋に接続したいことを意味します。
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]); // themeが変わると再接続してしまう:(
themeはどうでしょうか?
themeが変わるたびに、チャットルームに再接続する必要はありません。themeはリアクティブであってはいけないのです。つまりエフェクト内のロジックではなく、イベントハンドラであるべきなのです。
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]);
ルームに接続するためのロジックはリアクティブであるべきであり、エフェクトの中にあるべきです。
コードがリアクティブな値の変化に「キャッチアップ」するようにし、roomIdが変われば再度実行されるようにしたいのです。
イベントハンドラロジックはリアクティブな値が変わったからと言って、再び実行されるべきではないため、リアクティブではないです。
showNotificationはthemeの値が変わるたびに実行される必要はなく、chatルームに接続した時に実行されるイベントハンドラです。
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
themeのような非リアクティブなロジックはリアクティブなエフェクトのコードから分離するべきなのです。useEffectEventを使うことで、エフェクトからこの非リアクティブなロジックを分離することが可能になります!
最後に
useEffectEvent、使ってみたくなりましたか?
Stable版への正式組み込みはまだ先になりそう?ですが、useEffectEvent は今後の React 開発で便利に使える機能になりそうです。
[お知らせ]
Sapeetでは、Reactや最新のフロントエンド技術を活用しながらプロダクト開発に取り組んでいます。もしこの記事を読んで「一緒に開発してみたい」と思った方は、ぜひ採用情報もチェックしてみてください!
参考文献
https://ja.react.dev/learn/separating-events-from-effects
https://ja.react.dev/reference/react/experimental_useEffectEvent#noun-labs-1201738-(2)
https://ja.react.dev/learn/reusing-logic-with-custom-hooks
