带有 socket.io 的 React-Redux 和 Websockets [英] React-Redux and Websockets with socket.io

查看:18
本文介绍了带有 socket.io 的 React-Redux 和 Websockets的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 React-Redux 技术的新手,我想在一些实现方面得到您的帮助.

I'm new with that technology React-Redux and I would like your help with some implementation.

我想用套接字 (socket.io) 实现一个聊天应用程序.首先,用户必须注册(我在服务器端使用passport),然后,如果注册成功,用户必须连接到webSocket.

I want to implement one chat application with sockets (socket.io). First, the user has to sign up (I use passport in the server side) and after, if the sign up is successful the user has to connect to the webSocket.

我认为最好的方法是使用像管道这样的中间件来处理所有操作,并根据获取中间件的操作类型来做不同的事情.

I thought that the best will be to use a middleware like a pipe for all the actions and depending of what kind of action gets the middleware, do different things.

如果操作类型是 AUTH_USER,则创建客户端-服务器连接并设置来自服务器的所有事件.

If the action type is AUTH_USER, create client-server connection and set up all the events which will come from the server.

如果动作类型是MESSAGE,则向服务器发送消息.

If the action type is MESSAGE send to the server the message.

代码片段:

----- socketMiddleware.js ----

import { AUTH_USER,  MESSAGE } from '../actions/types';

import * as actions from 'actions/socket-actions';

import io from 'socket.io-client';

const socket = null;

export default function ({ dispatch }) {

    return next => action => {

        if(action.type == AUTH_USER) {

            socket = io.connect(`${location.host}`);

            socket.on('message', data => {

               store.dispatch(actions.addResponse(action.data));

            });

        }

        else if(action.type == MESSAGE && socket) {

            socket.emit('user-message', action.data);

            return next(action)

        } else {
            return next(action)
        }
    }

}

------ index.js -------

import {createStore, applyMiddleware} from 'redux';

import socketMiddleware from './socketMiddleware';



const createStoreWithMiddleware = applyMiddleware(

  socketMiddleware

)(createStore);

const store = createStoreWithMiddleware(reducer);

<Provider store={store}>

    <App />

</Provider>

您如何看待这种做法,是否是更好的实施方式?

What do you think about that practise, is it a better implementation?

推荐答案

剧透:我目前正在开发一个开源聊天应用程序.

Spoiler: I am currently developing what's going to be an open-source chat application.

通过将操作与中间件分开,甚至将套接字客户端与中间件分开,您可以更好地做到这一点.因此,结果是这样的:

You can do that better by separating actions from the middleware, and even the socket client from the middleware. Hence, resulting in something like this:

  • 类型 -> 每个请求的请求、成功、失败类型(非强制性).
  • Reducer -> 存储不同的状态
  • 操作 -> 发送操作以连接/断开连接/发出/监听.
  • 中间件 -> 处理您的操作,并将当前操作传递或不传递给套接字客户端
  • 客户端 -> 套接字客户端 (socket.io).
  • Types -> REQUEST, SUCCESS, FAILURE types for every request (not mandatory).
  • Reducer -> to store different states
  • Actions -> send actions to connect / disconnect / emit / listen.
  • Middleware -> to treat your actions, and pass or not the current action to the socket client
  • Client -> socket client (socket.io).

下面的代码取自正在开发的真实应用程序(有时略有编辑),它们对于大多数情况来说已经足够了,但是像 SocketClient 这样的某些东西可能不是 100% 完整的.

操作

您希望操作尽可能简单,因为它们通常是重复的工作,而且您最终可能会得到很多.

You want actions to be as simple as possible, since they are often repeated work and you'll probably end up having lots of them.

export function send(chatId, content) {
  const message = { chatId, content };
  return {
    type: 'socket',
    types: [SEND, SEND_SUCCESS, SEND_FAIL],
    promise: (socket) => socket.emit('SendMessage', message),
  }
}

请注意,socket 是一个参数化函数,这样我们就可以在整个应用程序中共享同一个 socket 实例,而不必担心任何导入(稍后我们将展示如何执行此操作).

Notice that socket is a parametrized function, this way we can share the same socket instance throughout the whole application and we don't have to worry about any import whatsoever (we'll show how to do this later).

中间件(socketMiddleware.js):

我们将使用与 erikras/react-redux-universal-hot-example 使用,但用于套接字而不是 AJAX.

We'll use a similar strategy as erikras/react-redux-universal-hot-example uses, though for socket instead of AJAX.

我们的套接字中间件将只负责处理套接字请求.

Our socket middleware will be responsible for processing only socket requests.

中间件将动作传递给套接字客户端,并分派:

Middleware passes the action onto the socket client, and dispatches:

  • REQUEST (action types[0]):正在请求(action.type 被发送到 reducer).
  • SUCCESS (action types[1]):请求成功时(action.type 和服务器响应作为 action.result 被发送到减速器).
  • FAILURE (action types[2]):请求失败时(action.type 和服务器响应为 action.error 被发送到减速器).
  • REQUEST (action types[0]): is requesting (action.type is sent to reducer).
  • SUCCESS (action types[1]): on request success (action.type and server response as action.result is sent to reducer).
  • FAILURE (action types[2]): on request failure (action.type and server response as action.error are sent to reducer).
export default function socketMiddleware(socket) {
  // Socket param is the client. We'll show how to set this up later.
  return ({dispatch, getState}) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }

    /*
     * Socket middleware usage.
     * promise: (socket) => socket.emit('MESSAGE', 'hello world!')
     * type: always 'socket'
     * types: [REQUEST, SUCCESS, FAILURE]
     */
    const { promise, type, types, ...rest } = action;

    if (type !== 'socket' || !promise) {
      // Move on! Not a socket request or a badly formed one.
      return next(action);
    }

    const [REQUEST, SUCCESS, FAILURE] = types;
    next({...rest, type: REQUEST});

    return promise(socket)
      .then((result) => {
        return next({...rest, result, type: SUCCESS });
      })
      .catch((error) => {
        return next({...rest, error, type: FAILURE });
      })
  };
}

SocketClient.js

唯一一个可以加载和管理 socket.io 客户端.

The only one that will ever load and manage the socket.io-client.

[可选](见下面代码中的 1).socket.io 一个非常有趣的特性是你可以拥有 消息确认,这将是执行 HTTP 请求时的典型回复.我们可以使用它们来验证每个请求是否正确.请注意,为了使用此功能,服务器 socket.io 命令也必须具有此最新确认参数.

[optional] (see 1 below in the code). One very interesting feature about socket.io is the fact that you can have message acknowledgements, which would be the typical replies when doing an HTTP request. We can use them to verify that each request was correct. Note that in order to make use of this feature server socket.io commands do also have to have this latest acknowledgement parameter.

import io from 'socket.io-client';

// Example conf. You can move this to your config file.
const host = 'http://localhost:3000';
const socketPath = '/api/socket.io';

export default class socketAPI {
  socket;

  connect() {
    this.socket = io.connect(host, { path: socketPath });
    return new Promise((resolve, reject) => {
      this.socket.on('connect', () => resolve());
      this.socket.on('connect_error', (error) => reject(error));
    });
  }

  disconnect() {
    return new Promise((resolve) => {
      this.socket.disconnect(() => {
        this.socket = null;
        resolve();
      });
    });
  }

  emit(event, data) {
    return new Promise((resolve, reject) => {
      if (!this.socket) return reject('No socket connection.');

      return this.socket.emit(event, data, (response) => {
        // Response is the optional callback that you can use with socket.io in every request. See 1 above.
        if (response.error) {
          console.error(response.error);
          return reject(response.error);
        }

        return resolve();
      });
    });
  }

  on(event, fun) {
    // No promise is needed here, but we're expecting one in the middleware.
    return new Promise((resolve, reject) => {
      if (!this.socket) return reject('No socket connection.');

      this.socket.on(event, fun);
      resolve();
    });
  }
}

app.js

在我们的应用程序启动时,我们初始化 SocketClient 并将其传递给商店配置.

On our app start-up, we initialize the SocketClient and pass it to the store configuration.

const socketClient = new SocketClient();
const store = configureStore(initialState, socketClient, apiClient);

configureStore.js

我们将带有新初始化的 SocketClientsocketMiddleware 添加到 store 中间件(还记得我们告诉您的参数,我们稍后会解释吗?).

We add the socketMiddleware with our newly initialized SocketClient to the store middlewares (remember that parameter which we told you we would explain later?).

export default function configureStore(initialState, socketClient, apiClient) {
const loggerMiddleware = createLogger();
const middleware = [
  ...
  socketMiddleware(socketClient),
  ...
];

[没什么特别的]动作类型常量

没什么特别的 = 你通常会做什么.

Nothing special = what you would normally do.

const SEND = 'redux/message/SEND';
const SEND_SUCCESS = 'redux/message/SEND_SUCCESS';
const SEND_FAIL = 'redux/message/SEND_FAIL';

[没什么特别的]减速器

export default function reducer(state = {}, action = {}) {
  switch(action.type) {
    case SEND: {
      return {
        ...state,
        isSending: true,
      };
    }
    default: {
      return state;
    }
  }
}

这看起来可能需要做很多工作,但是一旦设置好就值得了.您的相关代码将更易于阅读和调试,并且更不容易出错.

It might look like a lot of work, but once you have set it up it is worth it. Your relevant code will be easier to read, debug and you will be less prone to make mistakes.

PS:您也可以通过 AJAX API 调用来遵循此策略.

PS: You can follow this strategy with AJAX API calls as well.

这篇关于带有 socket.io 的 React-Redux 和 Websockets的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆