STOMPは、RabbitMQ、ActiveMQ、Apolloなどの人気のサーバーで実装されている、信頼性のある送信メッセージのための非常にシンプルで強力なプロトコルです。WebSocketを介したSTOMPを使用することで、AMQPのようなプロトコルとしては、TCP接続を許可しないブラウザーに制約されることなく、ウェブブラウザからのメッセージ送信に人気のある選択肢になります。
STOMP over WebSocketを使用するには、@stomp/stompjsを使用することができますが、これには厄介なコールバックと複雑なAPIがあり、より専門的な使用形態に合わせています。幸いにも、より有名ではない@stompjs/rx-stompがあり、RxJSの観察可能な接口を提供しています。観察可能な概念はAngular独自ではなく、Reactでの動作にも非常に似ています。これは、異なるメッセージ源が多数含まれる複雑なワークフローやパイプラインの作曲にIDEFに似ています。
このチュートリアルは、Angularの最初のバージョンと似たようなパスを進めますが、コンポーネント構造とコードスタイルは、Reactの機能的スタイルに調整されています。
注:このチュートリアルはstrict
TypeScriptで書かれていますが、JS版のコードは几乎同じであるため、型のインポートと定義をスキップすることができます。
目次
目標
ここで、RxStompのさまざまな側面を異なるコンポーネントで示す简化されたチャットルームアプリケーションを作成します。全体的な目標は以下の通りです。
-
ReactフロントエンドをSTOMPサーバーにRxStompと接続します。
-
STOMPサーバーとの接続状態に基づいたライブ接続状態表示です。
-
任意の設定可能なトピックに対するPub/Subロジックです。
-
RxStompロジックを複数のコンポーネントに分割し、ロジックと責任の分離方法を示します。
-
RxStompの接続/購読ライフサイクルをReactコンポーネントのライフサイクルと合わせることで、漏洩や未閉じられた观的察者を Ensureします。
前提条件
-
Reactアプリケーションが接続できるSTOMPサーバーを実行していること。ここでは、
rabbitmq_web_stomp
エクステンションを使用してRabbitMQを使用します。 -
最新のReactバージョン。このチュートリアルではv18を使用しますが、古いバージョンもおそらく機能します。
-
观的察者についての少しの熟悉さが役立つこともあります。
RabbitMQを使用したSTOMPサーバーのスターター
RabbitMQも使用したい場合は(厳密に必要ではありません)、以下は異なるオペレーティングシステムについてのインストールガイドです。エクステンションを追加するには、以下のコマンドを実行する必要があります:
$ rabbitmq-plugins enable rabbitmq_web_stomp
Dockerを使用できる場合、このようなDockerファイルのDockerをここに似せることで、チュートリアルに必要なすべてのものを設定できます:
FROM rabbitmq:3.8.8-alpine
run rabbitmq-plugins enable --offline rabbitmq_web_stomp
EXPOSE 15674
スターター リアクト テンプレート
このチュートリアルでは、Viteのreact-ts
テンプレートを使用します。私たちのアプリケーションの中心部分はApp
コンポーネントにあり、他の特定のSTOMP機能について子コンポーネントを作成します。
RxStompのインストール方法
ここでは、@stomp/rx-stomp
npmパッケージを使用します。
$ npm i @stomp/rx-stomp rxjs
これにより、2.0.0
バージョンがインストールされます。
注意: このチュートリアルはrxjs
を明示的に指定しなくても機能しますが、それを明示的にするのは良い慣習です。
STOMPサーバーとの接続と切断の管理方法
では、App.tsxを開いて、RxStomp
クライアントを初期化しましょう。クライアントは渲染に影響を与えない状態ではありませんので、useRef
フックに包んでいます。
// src/App.tsx
import { useRef } from 'react'
import { RxStomp } from '@stomp/rx-stomp'
import './App.css'
function App() {
const rxStompRef = useRef(new RxStomp())
const rxStomp = rxStompRef.current
return (
<>
<h1>Hello RxStomp!</h1>
</>
)
}
export default App
デフォルトのポートと認証詳細を想定して、次に接続のための設定を定義します。
// src/App.tsx
import { RxStomp } from '@stomp/rx-stomp'
import type { RxStompConfig } from '@stomp/rx-stomp'
...
const rxStompConfig: RxStompConfig = {
brokerURL: 'ws://localhost:15674/ws',
connectHeaders: {
login: 'guest',
passcode: 'guest',
},
debug: (msg) => {
console.log(new Date(), msg)
},
heartbeatIncoming: 0,
heartbeatOutgoing: 20000,
reconnectDelay: 200,
}
function App() {
...
開発体验を向上させるために、すべてのメッセージに時間スタンプをつけてローカルコンソールに出力し、タイマーの周波数を低く設定しました。您的設定はプロダクションアプリケーションには大きく異なるため、RxStompConfig 文档を参照して利用可能なすべてのオプションを確認してください。
次に、useEffect
フック内でrxStomp
に設定を渡します。これにより、コンポーネントライフサイクルとともに接続のアクティベーションが管理されます。
// src/App.tsx
...
function App() {
const rxStompRef = useRef(new RxStomp())
const rxStomp = rxStompRef.current
useEffect(() => {
rxStomp.configure(rxStompConfig)
rxStomp.activate()
return () => {
rxStomp.deactivate()
}
})
...
アプリに視覚的な変化はありませんが、ログを確認すると接続とピンのログが表示されるはずです。以下はその例です。
Date ... >>> CONNECT
login:guest
passcode:guest
accept-version:1.2,1.1,1.0
heart-beat:20000,0
Date ... Received data
Date ... <<< CONNECTED
version:1.2
heart-beat:0,20000
session:session-EJqaGQijDXqlfc0eZomOqQ
server:RabbitMQ/4.0.2
content-length:0
Date ... connected to server RabbitMQ/4.0.2
Date ... send PING every 20000ms
Date ... <<< PONG
Date ... >>> PING
注意: 一般的に、重複したログが表示される場合は、非アクティベーションや購読解除機能が正しく実装されていない可能性があります。React.StrictMode
を使用して、Reactは開発環境で各コンポーネントを2回レンダリングし、人々がこれらのバグを発見しやすくします。
接続ステータスを監視する方法
RxStompには、ブローカーとの可能な接続状態を表すRxStompState enumがあります。次の目標は、UIに接続ステータスを表示することです。
これのためにStatus.tsx
という新しいコンポーネントを作成しましょう。
// src/Status.tsx
import { useState } from 'react'
export default function Status() {
const [connectionStatus, setConnectionStatus] = useState('')
return (
<>
<h2>Connection Status: {connectionStatus}</h2>
</>
)
}
rxStomp.connectionState$
オブザーバブルを使用して、connectionStatus
文字列にバインドできます。useEffect
を使用した方法と同様に、アンマウントアクションでunsubscribe()
を使用します。
// src/Status.tsx
import { RxStompState } from '@stomp/rx-stomp'
import { useEffect, useState } from 'react'
import type { RxStomp } from '@stomp/rx-stomp'
export default function Status(props: { rxStomp: RxStomp }) {
const [connectionStatus, setConnectionStatus] = useState('')
useEffect(() => {
const statusSubscription = props.rxStomp.connectionState$.subscribe((state) => {
setConnectionStatus(RxStompState[state])
})
return () => {
statusSubscription.unsubscribe()
}
}, [])
return (
<>
<h2>Connection Status: {connectionStatus}</h2>
</>
)
}
それを表示するために、アプリに含めます。
// src/App.tsx
import Status from './Status'
...
return (
<>
<h1>Hello RxStomp!</h1>
<Status rxStomp={rxStomp}/>
</>
)
この時点で、お使いのスクリーンには機能するビジュアルインジケータが表示されているはずです。STOMPサーバーをダウンにしてみて、ログが预期通りに機能しているか確認してください。
メッセージの送信方法
ブローカーを使用した簡単なチャットルームを作成し、エンドツーエンドのメッセージングフローを簡略化して表示しましょう。
機能を新しいChatroom
コンポーネントに配置することができます。まず、入力に绑定于ける独自のusername
とmessage
フィールドをコンポーネントに作成することができます。
// src/Chatroom.tsx
import { useState } from 'react'
import type { RxStomp } from '@stomp/rx-stomp'
export default function Chatroom(props: {rxStomp: RxStomp}) {
const [message, setMessage] = useState('')
const [userName, setUserName] = useState(`user${Math.floor(Math.random() * 1000)}`)
return (
<>
<h2>Chatroom</h2>
<label htmlFor='username'>Username: </label>
<input
type='text'
name='username'
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
<label htmlFor='message'>Message: </label>
<input
type='text'
value={message}
onChange={(e) => setMessage(e.target.value)}
name='message'
/>
</>
)
}
これをAppに含め、チャットルームに参加するためのトグルを追加しましょう。
// src/App.tsx
import { useEffect, useState, useRef } from 'react'
import Chatroom from './Chatroom'
...
function App() {
const [joinedChatroom, setJoinedChatroom] = useState(false)
...
return (
<>
<h1>Hello RxStomp!</h1>
<Status rxStomp={rxStomp}/>
{!joinedChatroom && (
<button onClick={() => setJoinedChatroom(true)}>
Join chatroom!
</button>
)}
{joinedChatroom && (
<>
<button onClick={() => setJoinedChatroom(false)}>
Leave chatroom!
</button>
<Chatroom rxStomp={rxStomp}/>
</>
)}
</>
)
実際のメッセージ送信の時間です。STOMPはテキストベースのメッセージ(バイナリデータも可能です)を送信するには最適です。送信するデータの構造を新しいtypesファイルで定義しましょう。
// types.ts
interface ChatMessage {
userName: string,
message: string
}
注:TypeScriptを使用していない場合は、この型定義を追加する必要はないです。
次に、JSONを使用してメッセージを序列化し、.publish
を使用してデスクトップトピックとJSONbody
でSTOMPサーバーにメッセージを送信しましょう。
// src/Chatroom.tsx
import type { ChatMessage } from './types'
...
const CHATROOM_NAME = '/topic/test'
export default function Chatroom(props: {rxStomp: RxStomp}) {
...
function sendMessage(chatMessage: ChatMessage) {
const body = JSON.stringify({ ...chatMessage })
props.rxStomp.publish({ destination: CHATROOM_NAME, body })
console.log(`Sent ${body}`)
setMessage('')
}
return (
<>
<h2>Chatroom</h2>
<label htmlFor="username">Username: </label>
<input
type="text"
name="username"
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
<label htmlFor="message">Message: </label>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
name="message"
/>
<button onClick={() => sendMessage({userName, message})}>Send Message</button>
</>
)
}
テストのために、送信ボタンをいくつかクリックして、序列化が正しいか確認してください。まだビジュアル変更は見られないかもしれませんが、コンソールログはそれを示すはずです。
Date ... >>> SEND
destination:/topic/test
content-length:45
Sent {"userName":"user722","message":"1234567890"}
メッセージの受信方法
新しいコンポーネントを作成して、すべてのユーザーからのメッセージリストを表示します。今のところ、同じ型を使用し、トピック名をプロップとして渡し、すべてをリストとして表示します。これらはすべて、MessageList
という新しいコンポーネントに入れます。
// src/MessageDisplay.tsx
import { useEffect, useState } from 'react'
import type { RxStomp } from '@stomp/rx-stomp'
import type { ChatMessage } from './types'
export default function MessageDisplay(props: {rxStomp: RxStomp, topic: string}) {
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([
{userName: 'admin', message: `Welcome to ${props.topic} room!`}
])
return(
<>
<h2>Chat Messages</h2>
<ul>
{chatMessages.map((chatMessage, index) =>
<li key={index}>
<strong>{chatMessage.userName}</strong>: {chatMessage.message}
</li>
)}
</ul>
</>
)
}
すべてをまとめる時間です!
Status
コンポーネントと同様に、マウント時にサブスクリプションを設定し、アンマウント時に解除します。
RxJSのpipe
とmap
を使用して、JSONをChatMessage
にデシリアライズできます。モジュール型のデザインでは、必要に応じてRxJS
演算子を使用してより複雑なパイプラインを設定できます。
// src/MessageDisplay.tsx
...
import { map } from 'rxjs'
export default function MessageDisplay(props: {rxStomp: RxStomp, topic: string}) {
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([
{userName: 'admin', message: `Welcome to ${props.topic} room!`}
])
useEffect(() => {
const subscription = props.rxStomp
.watch(props.topic)
.pipe(map((message) => JSON.parse(message.body)))
.subscribe((message) => setChatMessages((chatMessages) => [...chatMessages, message]))
return () => {
subscription.unsubscribe()
}
}, [])
...
この時点で、チャットGUIはメッセージを正しく表示するはずです。異なるユーザーとして複数のタブを開いて実験してみてください。
ここで試してみたいことは、STOMPサーバーをオフにして、いくつかのメッセージを送信し、再度オンにすることです。メッセージはローカルにキューに入れられ、サーバーが準備ができたらディスパッチされるはずです。素晴らしい!
まとめ
このチュートリアルでは:
-
快適な開発体験のために
@stomp/rx-stomp
をインストールしました。 -
接続詳細、デバッガーログ、タイマー設定でクライアントを構成するために
RxStompConfig
を設定しました。 -
クライアントの主要なライフサイクルを管理するために、
rxStomp.activate
およびrxStomp.deactivate
を使用しました。 -
rxStomp.connectionState$
observableを使用して購読状態を監視しました。 -
rxStomp.publish
を使用して、構成可能な宛先とメッセージ本文でメッセージを発行しました。 -
rxStomp.watch
を使用して、与えられたトピックに対応するobservableを作成しました。 -
コンソールログとReactコンポーネントを使用して、ライブラリの動作を確認し、機能と不具合の Toleranceを確認しました。
最終的なコードはGitlabにあります: https://gitlab.com/harsh183/rxstomp-react-tutorial。それをスターターテンプレートとして使用し、発生する可能な問題を報告する的自由にお願いいたします。
Source:
https://www.freecodecamp.org/news/build-chat-app-with-stomp-and-react/