如何将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代码有三个明显错误:
- 用
map操作符返回异步Promise时,流最终输出的是Promise对象而非普通action,redux-observable无法正常识别派发,需要换成switchMap/mergeMap这类支持打平异步流的操作符。 - 登录成功后返回的action类型仍然是
LOGIN,会重复触发当前epic,造成无限循环,需要区分触发action、成功action、失败action的类型。 - 模块顶层直接调用
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
相关产品推荐
相关产品推荐

