在 React/Redux 应用程序中将 SignalR 集线器放在哪里?

IT技术 javascript reactjs redux signalr
2021-05-03 06:24:13

我正在设计一个使用 Redux 作为状态存储的 React 网站,主要是向用户显示当前项目的数量,使用实时更新来使用 SignalR 更新项目数量。

我想要这样做的方法是让 SignalR 发送项目更新消息以在您连接到服务器集线器时初始化起始人口,以及随着时间的推移通过相同的消息类型进行更新。我会有一个函数,它接受 SignalR 消息并将其转换为 Redux 操作并分派到 Redux 存储,然后使用该操作更新状态,然后更新 UI。

所以这个想法是

1) 连接到 SignalR 服务器集线器,为 ItemUpdate 消息设置客户端处理函数

2) 当服务器从客户端接收到 Connect() 时,它会为种群中的所有当前项目发送 ItemUpdate 消息

3) 客户端从 SignalR 接收这些消息,转换为动作并分派到 Redux 存储

4)Redux根据新的item信息更新store,UI显示出来

5) Server 意识到一个 item 已经添加或更新,并发送一个新的 ItemUpdate 消息给客户端进行更新

6) 重复

但是,我不确定应该将集线器单例放在何处,因为这似乎与 React/Redux 设计背道而驰。有人可以建议最好的方法吗?

我的主要应用

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import './index.css';
import registerServiceWorker from './registerServiceWorker';
import 'rxjs';
import store from './store/index';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root') as HTMLElement
);

registerServiceWorker();

我的商店创建文件

import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers/index';
import signalRMiddleware from '../signalr/middleware';

const store = createStore(rootReducer, applyMiddleware(signalRMiddleware));
export default store;

我的出站 SignalR 消息到服务器的中间件(注释掉,因为我无权访问集线器对象,我需要它来工作

export default function signalRMiddleware(store: any) {
    return (next: any) => (action: any) => {
        if (action.signalR) {
            switch (action.type) {
                default:
                    {
                        //const myCurrentState = store.getState().objectWithinState;
                        //_hub.server.methodOnTheServer2(action.type, myCurrentState);
                    }
            }
        }
        return next(action);
    }
}

现在对于传入的消息......这是我从在线示例中获得的signalR启动函数的外壳 - 尚未实现,因为我还没有集线器和连接并且不确定它应该去哪里

export function signalRStart(store: any, callback: Function) {
    _hub = $.connection.myHubName;

    _hub.client.firstClientFunction = (p1: any) => {
        store.dispatch({ type: "SERVER_CALLED_ME", a: p1 });
    }

    _hub.client.secondClientFunction = (p1: string, p2: string) => {
            store.dispatch({ type: "SERVER_CALLED_ME_2", value: p1 + p2 });
        }
    }

    $.connection.hub.start(() => callback());
}

这是网站上给出的示例,我找到了将所有代码联系在一起的代码,但是我没有看到它如何像在我的主索引页面中那样与 React/Redux 集成,我必须将创建的商店传递给提供者组件,因此我不能将集线器创建放在此下方,因为您需要传递到商店创建中的信号器中间件组件的集线器

let _hub;

let store = createStore(
  todoApp,
  // applyMiddleware() tells createStore() how to handle middleware
  applyMiddleware(signalRMiddleware)
)

// Make sure signalr is connected
signalRStart(store, () => {
    render((...),
    document.getElementById("app-container"));
});

有人可以建议将 SignalR 集成到我的 React/Redux 应用程序的最佳方式吗?

2个回答

对于将来可能会找到此线程的人。

这是我的自定义中间件,它只建立连接并注册处理程序。请注意,我只想接收数据,对发送数据不感兴趣。

import {
  JsonHubProtocol,
  HttpTransportType,
  HubConnectionBuilder,
  LogLevel
} from '@aspnet/signalr'; // version 1.0.4

// action for user authentication and receiving the access_token
import { USER_SIGNED_IN } from '../actions/auth';

const onNotifReceived = res => {
  console.log('****** NOTIFICATION ******', res);
};

const startSignalRConnection = connection => connection.start()
  .then(() => console.info('SignalR Connected'))
  .catch(err => console.error('SignalR Connection Error: ', err));

const signalRMiddleware = ({ getState }) => next => async (action) => {
  // register signalR after the user logged in
  if (action.type === USER_SIGNED_IN) {
    const urlRoot = (window.appConfig || {}).URL_ROOT;
    const connectionHub = `${urlRoot}/api/service/hub`;

    const protocol = new JsonHubProtocol();

    // let transport to fall back to to LongPolling if it needs to
    const transport = HttpTransportType.WebSockets | HttpTransportType.LongPolling;

    const options = {
      transport,
      logMessageContent: true,
      logger: LogLevel.Trace,
      accessTokenFactory: () => action.user.access_token
    };

    // create the connection instance
    const connection = new HubConnectionBuilder()
      .withUrl(connectionHub, options)
      .withHubProtocol(protocol)
      .build();

    // event handlers, you can use these to dispatch actions to update your Redux store
    connection.on('OperationProgress', onNotifReceived);
    connection.on('UploadProgress', onNotifReceived);
    connection.on('DownloadProgress', onNotifReceived);

    // re-establish the connection if connection dropped
    connection.onclose(() => setTimeout(startSignalRConnection(connection), 5000));

    startSignalRConnection(connection);
  }

  return next(action);
};

export default signalRMiddleware;

在我的 store.js 文件中

import signalRMiddleware from '../middlewares/signalRMiddleware';

...

createStore(rootReducer, {}, composeEnhancers(applyMiddleware(signalRMiddleware)));

2020 年 6 月更新 这就是我们现在使用新包 @microsoft/signalr https://stackoverflow.com/a/62162742/10232269 这样做的方式 这不是使用中间件方法。我们使用 Redux,但您不必使用 Redux 来使用此方法。

根据 Redux FAQ,websockets 和其他类似连接的正确位置是 Redux 中间件

这是现有 websocket 中间件的列表您可以查看其中几个的源代码,并很容易地了解如何实现您自己的自定义中间件:

中间件可以分派动作。这是一个套接字中间件可能是什么样子的示例,以及调度它侦听的操作:

const createMySocketMiddleware = (url) => {
    return storeAPI => {
        let socket = createMyWebsocket(url);

        socket.on("message", (message) => {
            storeAPI.dispatch({
                type : "SOCKET_MESSAGE_RECEIVED",
                payload : message
            });
        });

        return next => action => {
            if(action.type == "SEND_WEBSOCKET_MESSAGE") {
                socket.send(action.payload);
                return;
            }

            return next(action);
        }
    }
}

您需要将此中间件应用到您的 redux 商店

let store = createStore(
    some_reducer,
    applyMiddleware(createMySocketMiddleware)
)

稍后,在您的应用程序中。这是一个动作创建者

const sendSocketMessage = message => ({
    type : "SEND_WEBSOCKET_MESSAGE",
    payload : message
}

在你的组件中添加一个按钮以通过 websockets 调度一个动作

class MyComponent extends React.Component {
    handleClick = () => {
        this.props.sendSocketMessage("This goes to the server");
    }
}

export default connect(null, {sendSocketMessage})(MyComponent)