Skip to content

React 中使用 SignalR

前言

当遇到服务器需要实时主动向客户端推送数据的需求时,可以通过 WebSocket 协议实现,如果服务器采用 .NET 框架,我们就可以使用微软官方提供 SignalR 技术。

本文将演示如何在 React 项目如何使用 SignalR,并通过自定义 hook 来简化使用。

简单介绍下 SignalR

ASP.NET Core SignalR 是一个库,可用于简化向应用添加实时 Web 功能,让服务器端代码能够将内容推送到客户端。

SignalR 支持以下用于处理实时通信的技术(按正常回退的顺序):

SignalR 自动选择服务器和客户端能力范围内的最佳传输方法。

也就是说:当 WebSockets 可用时,SignalR 将在底层使用 WebSockets,而当不可用时,它会优雅地回退到其他技术,同时保持应用程序代码不变。

HttpTransportType 枚举如下:

typescript
export declare enum HttpTransportType {
  /** Specifies no transport preference. */
  None = 0,
  /** Specifies the WebSockets transport. */
  WebSockets = 1,
  /** Specifies the Server-Sent Events transport. */
  ServerSentEvents = 2,
  /** Specifies the Long Polling transport. */
  LongPolling = 4,
}

可以通过 HubConnectionBuilder 实例提供的 withUrl(string, HttpTransportType) 方法指定传输协议。 比如:.withUrl(hubUrl,signalR.HttpTransportType.WebSockets)

实现

Install

bash
yarn add @microsoft/signalr

Usage

  1. 创建实例:在组件挂载时,使用 HubConnectionBuilder 类,创建 HubConnection 实例;
  2. 建立连接:使用实例上的 start() 方法启动 SignalR 连接,返回的是一个 Promise<void> 所以可以做一些连接成功后续操作;
  3. 监听事件:使用实例上的 on(string, (args: any[]) => void) 方法来监听服务器上的事件,第一个参数是订阅的事件名称,第二个参数是回调函数,用于处理接收到的消息;
  4. 断开连接:在组件卸载时,使用实例上的 stop() 方法断开 SignalR 连接,返回的是一个 Promise<void> 所以可以做一些断开连接后续操作;

TIP

SignalR 连接可能会因多种原因中断(网络问题,服务器故障,客户端脚本错误或内存泄漏等等)。

可以通过 HubConnectionBuilder 的 withAutomaticReconnect() 方法,在连接丢失时自动尝试重新连接。

默认情况下,客户端将分别等待 0、2、10 和 30 秒,然后尝试 4 次重新连接尝试。

实现源码:

tsx
import { useState, useEffect } from 'react';
import * as signalR from '@microsoft/signalr';
import urls from 'services/urls';
import { useMount } from 'hooks';

export default function MyComponent() {
  const [hubConnection, setHubConnection] =
    useState<signalR.HubConnection | null>(null);

  useMount(() => {
    // 1.组件挂载时,创建 HubConnection 实例
    const newConnection = new signalR.HubConnectionBuilder()
      .withUrl(urls.hubUrl)
      .withAutomaticReconnect() // 自动重连
      .build();

    setHubConnection(newConnection);
  });

  useEffect(() => {
    // 2.启动 SignalR 连接
    if (hubConnection) {
      hubConnection
        .start()
        .then(() => {
          console.log('SignalR Connected!');
        })
        .catch((err) => {
          console.log('SignalR Connection Error: ', err);
        });
    }

    return () => {
      // 4.组件卸载时,断开 SignalR 连接
      if (hubConnection) {
        hubConnection
          .stop()
          .then(() => {
            console.log('SignalR Disconnected!');
          })
          .catch((err) => {
            console.log('SignalR Disconnection Error: ', err);
          });
      }
    };
  }, [hubConnection]);

  // 3.使用 hub 的 on 方法来监听服务器上的事件
  hubConnection?.on('ReceiveMessage', (data) => {
    console.log(`ReceiveMessage: ${data}`);
    // omit logic
  });

  return (
    <div>
      <h1>SignalR Example</h1>
    </div>
  );
}

useSignalR

使用自定义 hook 封装 SignalR 可以使代码更加简洁和可复用。

tsx
import { useState, useEffect } from 'react';
import * as signalR from '@microsoft/signalr';
import type { Urls } from 'types';
import { useMount } from 'hooks';

export default function useSignalR(hubUrl: Urls) {
  const [hubConnection, setHubConnection] =
    useState<signalR.HubConnection | null>(null);

  useMount(() => {
    const newConnection = new signalR.HubConnectionBuilder()
      .withUrl(urls.hubUrl)
      .withAutomaticReconnect()
      .build();

    setHubConnection(newConnection);
  });

  useEffect(() => {
    if (hubConnection) {
      hubConnection
        .start()
        .then(() => {
          console.log('SignalR Connected!');
        })
        .catch((err) => {
          console.log('SignalR Connection Error: ', err);
        });
    }

    return () => {
      if (hubConnection) {
        hubConnection
          .stop()
          .then(() => {
            console.log('SignalR Disconnected!');
          })
          .catch((err) => {
            console.log('SignalR Disconnection Error: ', err);
          });
      }
    };
  }, [hubConnection]);

  return hubConnection;
}

使用 useSignalR hook

tsx
import useSignalR from './hook';
import urls from 'services/urls';

export default function MyComponent() {
  const hubConnection = useSignalR(urls.hubUrl);

  // 使用 hubConnection.on 方法进行事件监听
  hubConnection?.on('ReceiveMessage', (data) => {
    console.log(`ReceiveMessage: ${data}`);
    // omit logic
  });
   return (
    // omit rendering logic
   )
}

参考

https://dotnet.microsoft.com/en-us/apps/aspnet/signalr

https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API

https://medium.com/swlh/creating-a-simple-real-time-chat-with-net-core-reactjs-and-signalr-6367dcadd2c6