少数ではありますが、依然として膨大な数のプロジェクトでは、データを再取得することなく、変更に対するインターフェイスの即時反応を提供するために Web ソケットの統合が必要です。 これは重要なことなので、開発エクスペリエンスを向上させるための API を提供するサードパーティ ライブラリについて話したり、比較したりするつもりはありません。 私の目標は、 NextJs と迅速に統合する方法を示すことです。そして、開発中に直面した問題をどのように解決するか。 @microsoft/signalr 皆さんがすでに NextJS プロジェクトをローカルにインストールしてデプロイしていることを願っています。私の場合、バージョンは です。さらに重要なライブラリをいくつか追加しましょう。データ取得用の (バージョン ) と、ローカル キャッシュと Web ソケット用の API である (バージョン ) をさらに操作します。 13.2.4 swr 2.1.5 @microsoft/signalr 7.0.5 npm install --save @microsoft/signalr swr まず、単純な 関数と、REST API から初期データを取得する という新しいフックを作成します。チャットのメッセージのリスト、エラーと読み込み状態を検出するフィールド、キャッシュされたデータを変更できるメソッド を返します。 fetcher useChatData mutate // hooks/useChatData.ts import useSWR from 'swr'; type Message = { content: string; createdAt: Date; id: string; }; async function fetcher<TResponse>(url: string, config: RequestInit): Promise<TResponse> { const response = await fetch(url, config); if (!response.ok) { throw response; } return await response.json(); } export const useChatData = () => { const { data, error, isLoading, mutate } = useSWR<Message[]>('OUR_API_URL', fetcher); return { data: data || [], isLoading, isError: error, mutate, }; }; 想定どおりに動作することをテストするために、ページ コンポーネントを更新しましょう。上部のフックをインポートし、以下のスニペットのようにそこからデータを抽出します。機能する場合は、レンダリングされたデータが表示されます。ご覧のとおり、非常にシンプルです。 // pages/chat.ts import { useChatData } from 'hooks/useChatData'; const Chat: NextPage = () => { const { data } = useChatData(); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); }; 次のステップでは、将来のページを Web ソケットに接続し、 イベントをキャッチし、新しいメッセージでキャッシュを更新する必要があります。別のファイルでソケット サービスを構築することから始めることを提案します。 NewMessage SignalR ドキュメントの例によると、イベントをさらにリッスンするために接続のインスタンスを作成する必要があります。また、重複を防ぐための接続オブジェクトと、接続を開始/停止するための 2 つのヘルパーも追加しました。 // api/socket.ts import { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr'; let connections = {} as { [key: string]: { type: string; connection: HubConnection; started: boolean } }; function createConnection(messageType: string) { const connectionObj = connections[messageType]; if (!connectionObj) { console.log('SOCKET: Registering on server events ', messageType); const connection = new HubConnectionBuilder() .withUrl('API_URL', { logger: LogLevel.Information, withCredentials: false, }) .withAutomaticReconnect() .build(); connections[messageType] = { type: messageType, connection: connection, started: false, }; return connection; } else { return connections[messageType].connection; } } function startConnection(messageType: string) { const connectionObj = connections[messageType]; if (!connectionObj.started) { connectionObj.connection.start().catch(err => console.error('SOCKET: ', err.toString())); connectionObj.started = true; } } function stopConnection(messageType: string) { const connectionObj = connections[messageType]; if (connectionObj) { console.log('SOCKET: Stoping connection ', messageType); connectionObj.connection.stop(); connectionObj.started = false; } } function registerOnServerEvents( messageType: string, callback: (payload: Message) => void, ) { try { const connection = createConnection(messageType); connection.on('NewIncomingMessage', (payload: Message) => { callback(payload); }); connection.onclose(() => stopConnection(messageType)); startConnection(messageType); } catch (error) { console.error('SOCKET: ', error); } } export const socketService = { registerOnServerEvents, stopConnection, }; したがって、ページはコード スニペットのようになります。メッセージのリストを含む フェッチおよび抽出し、レンダリングします。また、上記の イベントを登録し、接続を作成し、バックエンドをリッスンします。 data useEffect NewMessage イベントがトリガーされると、フックの メソッドによって既存のリストが新しいオブジェクトで更新されます。 mutate // pages/chat.ts import { useChatData } from 'hooks/useChatData'; import { socketService } from 'api/socket'; const Chat: NextPage = () => { const { data } = useChatData(); useEffect(() => { socketService.registerOnServerEvents( 'NewMessage', (payload: Message) => { mutate(() => [...data, payload], { revalidate: false }); } ); }, [data]); useEffect(() => { return () => { socketService.stopConnection('NewMessage'); }; }, []); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); }; 私には良さそうですし、機能します。新しいメッセージがフィードにどのように表示されるかがわかります。明確で理解しやすいため、チャットを使用した基本的な例を選択しました。そしてもちろん、それを独自のロジックに適用します。 小さなボーナス いずれかのバージョン ( ) を使用すると、重複の問題が発生しました。これは依存関係配列 に接続されていました。依存関係が変更されるたびに、 コールバックをキャッシュし、何度もトリガーしました。 @microsoft/signalr useEffect connection.on(event, callback); useEffect(() => { // data equals [] by default (registerOnServerEvents 1 run), // but after initial data fetching it changes (registerOnServerEvents 2 run) // each event changes data and triggers runnning of registerOnServerEvents socketService.registerOnServerEvents( 'NewMessage', // callback cached (payload: Message) => { // mutate called multiple times on each data change mutate(() => [...data, payload], { revalidate: false }); } ); }, [data]); // after getting 3 messages events, we had got 4 messages rendered lol 私たちが見つけた最も迅速で信頼性の高い解決策は、React 内にデータのコピーを保持し、将来の更新のために 内でそれを使用することでした。 ref useEffect // pages/chat.ts import { useChatData } from 'hooks/useChatData'; import { socketService } from 'api/socket'; const Chat: NextPage = () => { const { data } = useChatData(); const messagesRef = useRef<Message[]>([]); useEffect(() => { messagesRef.current = chatData; }, [chatData]); useEffect(() => { socketService.registerOnServerEvents( 'NewMessage', (payload: Message) => { const messagesCopy = messagesRef.current.slice(); mutate(() => [...messagesCopy, payload], { revalidate: false }); } ); }, [data]); useEffect(() => { return () => { socketService.stopConnection('NewMessage'); }; }, []); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); }; 現在、必要な修正がすでに含まれていると思われる の新しいバージョンを使用しています。とにかく、誰かがこの解決策が役立つと考えて、この回避策を使用した場合、私は幸せになります。結論として、SignalR に関する私の経験は非常に肯定的であり、インストールには特定の依存関係や設定は必要なく、問題なく動作し、ニーズを満たしていると言いたいと思います。 @microsoft/signalr