使用socket.io的React-Redux和Websockets [英] React-Redux and Websockets with socket.io

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

问题描述

我是React-Redux的新技术,希望您在实施方面有所帮助.

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

我认为最好的方法是对所有操作使用管道之类的中间件,并根据获取中间件的操作类型来执行不同的操作.

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

如果操作类型为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>

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

解决方案

扰流器:我目前正在开发将成为开源聊天应用程序的东西.

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

  • 类型->每个请求的请求,成功,失败类型(不是强制性的).
  • 减速器->存储不同的状态
  • 动作->发送动作以连接/断开/发出/监听.
  • 中间件->处理您的操作,并将当前操作传递或不传递给套接字客户端
  • 客户端->套接字客户端(socket.io).

下面的代码取材于正在开发的真实应用程序(有时进行了少量编辑),它们足以应付大多数情况,但某些诸如SocketClient之类的东西可能还没有100%完整.

em>

操作

您希望动作尽可能简单,因为它们经常是重复的工作,并且您最终可能会拥有很多动作.

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

请注意,套接字是一个参数化函数,通过这种方式,我们可以在整个应用程序中共享相同的套接字实例,而不必担心任何导入(稍后将介绍如何执行此操作).

中间件(socketMiddleware.js):

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

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

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

  • 请求(动作types[0]):正在请求(action.type发送到减速器).
  • 成功(操作types[1]):在请求成功时(action.type和服务器action.result响应发送到reducer).
  • FAILURE(操作types[2]):请求失败(action.type和服务器响应action.error发送到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-client的服务器.

[可选](请参见代码下面的1).关于socket.io的一个非常有趣的功能是您可以拥有 解决方案

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:

  • 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).

The code below is taken from the real app which is under development (sometimes slightly edited), and they are enough for the majority of situations, but certain stuff like the SocketClient might not be 100% complete.

Actions

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),
  }
}

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).

Middleware (socketMiddleware.js):

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]): 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

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

[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

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

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] Action types constants

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';

[Nothing special] Reducer

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: You can follow this strategy with AJAX API calls as well.

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

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