You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将SignalR接入redux-observable?附代码及选型疑问

问题描述

正在开发接入SignalR的React应用,计划使用SignalR实现应用认证功能,目前在将SignalR连接接入redux-observable时遇到阻碍,已编写的代码如下:

import {switchMap, Observable, map} from 'rxjs';
import {ofType} from 'redux-observable';
import {HubConnectionBuilder, LogLevel} from "@microsoft/signalr";

const connection = new HubConnectionBuilder()
.withUrl(process.env.REACT_APP_API_HUB_URL)
.configureLogging(LogLevel.Error)
.withAutomaticReconnect()
.build();

connection.start().then((result) => {
   console.log("Connected.");
})

export const signalrEpic = action$ => action$.pipe(
   ofType('LOGIN'),
   map(async(action) => {
    console.log("Logging....");
    const {email, password, deviceID} = action.payload;
    await connection.invoke("Login", email, password, deviceID);
    return {type: 'LOGIN',};
   })
);

核心疑问:针对这类简单业务逻辑,应该继续使用redux-observable,还是直接改用redux-thunk实现?

参考结论

选型不需要非此即彼,按项目现有技术栈判断即可:

  • 如果项目已经全局使用redux-observable处理其他异步流逻辑,完全没必要为这一个场景切换到redux-thunk,当前代码只是写法有误,修正后可以正常运行。
  • 如果项目没有其他rxjs相关的使用场景,只需要处理简单的异步请求,选redux-thunk更轻量,学习和维护成本更低。
现有代码问题与实现参考

你当前的redux-observable代码有三个明显错误:

  1. 用map操作符返回异步Promise时,流最终输出的是Promise对象而非普通action,redux-observable无法正常识别派发,需要换成switchMap/mergeMap这类支持打平异步流的操作符。
  2. 登录成功后返回的action类型仍然是LOGIN,会重复触发当前epic,造成无限循环,需要区分触发action、成功action、失败action的类型。
  3. 模块顶层直接调用connection.start()容易出现时序问题,比如连接建立时鉴权参数还未准备好、收到服务端推送时前端状态还未初始化完成。

修正后的redux-observable可运行参考代码:

import { switchMap, of } from 'rxjs';
import { ofType } from 'redux-observable';
import { HubConnectionBuilder, LogLevel } from "@microsoft/signalr";

const connection = new HubConnectionBuilder()
  .withUrl(process.env.REACT_APP_API_HUB_URL)
  .configureLogging(LogLevel.Error)
  .withAutomaticReconnect()
  .build();

// 标记连接状态,避免重复启动连接
let hasConnected = false;
const initConnection = async () => {
  if (hasConnected) return;
  await connection.start();
  hasConnected = true;
  console.log("SignalR connected.");
}

export const signalrEpic = action$ => action$.pipe(
  ofType('LOGIN'),
  switchMap(async (action) => {
    console.log("Start login process....");
    const {email, password, deviceID} = action.payload;
    try {
      await initConnection();
      await connection.invoke("Login", email, password, deviceID);
      return { type: 'LOGIN_SUCCESS', payload: { email } };
    } catch (err) {
      return { type: 'LOGIN_FAILED', payload: err };
    }
  })
);

如果选择用redux-thunk实现,逻辑会更直观,不需要理解rxjs操作符的特性,示例代码如下:

import { HubConnectionBuilder, LogLevel } from "@microsoft/signalr";

const connection = new HubConnectionBuilder()
  .withUrl(process.env.REACT_APP_API_HUB_URL)
  .configureLogging(LogLevel.Error)
  .withAutomaticReconnect()
  .build();

let hasConnected = false;
const initConnection = async () => {
  if (hasConnected) return;
  await connection.start();
  hasConnected = true;
  console.log("SignalR connected.");
}

// thunk action
export const login = (email, password, deviceID) => async (dispatch) => {
  dispatch({ type: 'LOGIN_START' });
  try {
    await initConnection();
    await connection.invoke("Login", email, password, deviceID);
    dispatch({ type: 'LOGIN_SUCCESS', payload: { email } });
  } catch (err) {
    dispatch({ type: 'LOGIN_FAILED', payload: err });
  }
}

内容的提问来源于stack exchange,提问作者Tony

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 17:18:06