socket react reactjs socket.io redux middleware

reactjs - React-Redux y Websockets con socket.io



react redux socket (1)

Soy nuevo con esa tecnología React-Redux y me gustaría su ayuda con alguna implementación.

Quiero implementar una aplicación de chat con sockets (socket.io). Primero, el usuario tiene que registrarse (yo uso el pasaporte en el lado del servidor) y después, si la inscripción es exitosa, el usuario tiene que conectarse al webSocket.

Pensé que lo mejor sería usar un middleware como una canalización para todas las acciones y dependiendo de qué tipo de acción obtenga el middleware, haga cosas diferentes.

Si el tipo de acción es AUTH_USER , cree una conexión cliente-servidor y configure todos los eventos que provendrán del servidor.

Si el tipo de acción es MESSAGE envíe el mensaje al servidor.

Fragmentos de código:

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

¿Qué piensas de esa práctica, es una mejor implementación?


Spoiler: Actualmente estoy desarrollando lo que será una aplicación de chat de código abierto.

Puede hacerlo mejor separando las acciones del middleware, e incluso el cliente de socket del middleware. Por lo tanto, resultando en algo como esto:

  • Tipos -> Tipos de SOLICITUD, ÉXITO, FALLO para cada solicitud (no es obligatorio).
  • Reductor -> para almacenar diferentes estados
  • Acciones -> enviar acciones para conectar / desconectar / emitir / escuchar.
  • Middleware -> para tratar sus acciones, y pasar o no la acción actual al cliente socket
  • Cliente -> cliente socket (socket.io).

El código a continuación está tomado de la aplicación real que está en desarrollo (a veces ligeramente editada), y son suficientes para la mayoría de las situaciones, pero ciertas cosas como SocketClient pueden no estar completas al 100%.

Comportamiento

Desea que las acciones sean lo más simples posible, ya que a menudo se repite el trabajo y probablemente terminará teniendo muchas de ellas.

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

Tenga en cuenta que socket es una función parametrizada, de esta manera podemos compartir la misma instancia de socket en toda la aplicación y no tenemos que preocuparnos por ninguna importación en absoluto (le mostraremos cómo hacerlo más adelante).

Middleware (socketMiddleware.js):

Usaremos una estrategia similar a la de erikras/react-redux-universal-hot-example , aunque para socket en lugar de AJAX.

Nuestro middleware de socket será responsable de procesar solo las solicitudes de socket.

Middleware pasa la acción al cliente de socket y envía:

  • SOLICITUD ( types[0] acción types[0] ): está solicitando ( action.type se envía al reductor).
  • ÉXITO ( types[1] acciones types[1] ): en caso de éxito ( action.type y respuesta del servidor, ya que action.result se envía al reductor).
  • FALLO ( types[2] acción types[2] ): error de solicitud ( action.type y respuesta del servidor como action.error se envían al reductor).

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

El único que alguna vez cargará y administrará el socket.io-client.

[opcional] (ver 1 abajo en el código). Una característica muy interesante de socket.io es el hecho de que puede tener acuses de recibo de mensajes , que serían las respuestas típicas al realizar una solicitud HTTP. Podemos usarlos para verificar que cada solicitud fue correcta. Tenga en cuenta que para hacer uso de esta característica, los comandos socket.io del servidor también tienen que tener este último parámetro de confirmación.

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

En el inicio de nuestra aplicación, inicializamos el SocketClient y lo pasamos a la configuración de la tienda.

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

configureStore.js

socketMiddleware el socketMiddleware con nuestro socketMiddleware recién inicializado a los middlewares de la tienda (¿recuerdas el parámetro que te dijimos que explicaríamos más adelante?).

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

[Nada especial] Tipos de acción constantes

Nada especial = lo que normalmente harías.

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

[Nada especial] Reductor

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

Puede parecer mucho trabajo, pero una vez que lo haya configurado, merece la pena. Su código relevante será más fácil de leer, depurar y será menos propenso a cometer errores.

PD: También puedes seguir esta estrategia con llamadas a la API de AJAX.