web-dev-qa-db-fra.com

React-Redux et Websockets avec socket.io

Je suis nouveau avec cette technologie React-Redux et j'aimerais votre aide pour une implémentation.

Je veux implémenter une application de chat avec des sockets (socket.io). Tout d'abord, l'utilisateur doit s'inscrire (j'utilise un passeport côté serveur) et ensuite, si l'inscription réussit, l'utilisateur doit se connecter au webSocket.

Je pensais que le mieux serait d'utiliser un middleware comme une pipe pour toutes les actions et selon le type d'action qui obtient le middleware, faire des choses différentes.

Si le type d'action est AUTH_USER, crée une connexion client-serveur et configure tous les événements qui proviendront du serveur.

Si le type d'action est MESSAGE envoyez au serveur le message.

Extraits de code:

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

Que pensez-vous de cette pratique, est-ce une meilleure mise en œuvre?

21
Ganbel

Spoiler: Je développe actuellement ce qui va être une application de chat open source.

Vous pouvez mieux faire cela en séparant les actions du middleware, et même le client socket du middleware. Par conséquent, résultant en quelque chose comme ceci:

  • Types -> Types REQUEST, SUCCESS, FAILURE pour chaque requête (non obligatoire).
  • Réducteur -> pour stocker différents états
  • Actions -> envoyer des actions pour se connecter/déconnecter/émettre/écouter.
  • Middleware -> pour traiter vos actions, et transmettre ou non l'action en cours au client socket
  • Client -> client socket (socket.io).

Le code ci-dessous est tiré de la vraie application en cours de développement (parfois légèrement édité), et ils sont suffisants pour la majorité des situations, mais certaines choses comme le SocketClient peuvent ne pas être complètes à 100%. =

Actions

Vous voulez que les actions soient aussi simples que possible, car ce sont souvent des travaux répétés et vous finirez probablement par en avoir beaucoup.

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

Notez que socket est une fonction paramétrée, de cette façon, nous pouvons partager la même instance de socket dans toute l'application et nous n'avons à nous soucier d'aucune importation (nous montrerons comment le faire plus tard).

Middleware (socketMiddleware.js):

Nous utiliserons une stratégie similaire à celle utilisée par erikras/react-redux-universal-hot-example , bien que pour socket au lieu d'AJAX.

Notre middleware socket sera responsable du traitement des demandes de socket uniquement.

Le middleware transmet l'action au client de socket et envoie:

  • DEMANDE (action types[0]): demande (action.type est envoyé au réducteur).
  • RÉUSSITE (action types[1]): succès de la demande (action.type et la réponse du serveur comme action.result est envoyé au réducteur).
  • ÉCHEC (action types[2]): en cas d'échec de la demande (action.type et la réponse du serveur comme action.error sont envoyés au réducteur).
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

Le seul qui pourra jamais charger et gérer le socket.io-client.

[facultatif] (voir 1 ci-dessous dans le code). Une caractéristique très intéressante de socket.io est le fait que vous pouvez avoir accusé de réception de message , ce qui serait le réponses typiques lors d'une requête HTTP. Nous pouvons les utiliser pour vérifier que chaque demande était correcte. Notez que pour utiliser cette fonctionnalité, les commandes socket.io du serveur doivent également avoir ce dernier paramètre d'acquittement.

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

Au démarrage de notre application, nous initialisons le SocketClient et le passons à la configuration du magasin.

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

configureStore.js

Nous ajoutons le socketMiddleware avec notre SocketClient nouvellement initialisé aux middlewares du magasin (rappelez-vous ce paramètre que nous vous avons dit que nous expliquerions plus tard?).

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

[Rien de spécial] Constantes de types d'actions

Rien de spécial = ce que vous feriez normalement.

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

[Rien de spécial] Réducteur

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

Cela peut sembler beaucoup de travail, mais une fois que vous l'avez configuré, cela en vaut la peine. Votre code pertinent sera plus facile à lire, à déboguer et vous serez moins enclin à faire des erreurs.

PS: Vous pouvez également suivre cette stratégie avec AJAX appels API également.

32
zurfyx