我对React Redux这项技术是新手,我希望您能帮助我实现一些功能.

我想用sockets(socket.io)实现一个聊天应用程序.首先,用户必须注册(我在服务器端使用passport),然后,如果注册成功,用户必须连接到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>

你觉得这种做法怎么样?它是一种更好的实施方式吗?

推荐答案

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

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

  • 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

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

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实例,而不必担心任何导入(稍后我们将演示如何实现).

Middleware (socketMiddleware.js):

我们将使用与erikras/react-redux-universal-hot-example次使用类似的策略,不过对于socket而不是AJAX.

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

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

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

唯一一个将加载和管理套接字的.io客户端.

[optional] (see 1 below in the code).关于套接字的一个非常有趣的特性.io是指您可以有message acknowledgements条回复,这是执行HTTP请求时的典型回复.我们可以使用它们来验证每个请求是否正确.请注意,为了使用此功能,服务器套接字.io命令也必须有这个最新的确认参数.

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并将其传递给store 配置.

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

configureStore.js

我们将socketMiddleware和新初始化的SocketClient一起添加到store 中间件中(还记得我们告诉过您稍后将解释的参数吗?).

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

[Nothing special] Action types constants

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

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

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

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

Reactjs相关问答推荐

URL参数和React路由中的点

是否可能有不同的加载器用于路径有查询参数和没有查询参数,或者在React Router 6中添加它们时有条件地跳过一个加载器?

XChaCha20-Poly1305的解密函数

一键MUI导出

在数组对象内的数组上进行映射

状态更改时的rtk查询触发器

根据用户 Select 的注册类型更改表单字段

在 Next13 中将 props 传递给 onClick 从服务器到客户端组件

Yup.number().integer() 不将 1.0 视为小数,如何解决这个问题?

如何在不使用 Axios 的情况下将图像文件从 React.js 发送到 Spring Boot Rest API?

将外部 JavaScript 导入 React(通过文件)

在react 中单击父级中的按钮时如何将数据从子级发送到父级?

React - 使用 useEffect 还是直接在 onChange 方法中更改值对性能最好?

useEffect 渲染没有依赖数组的组件

为什么刷新页面时我的localStorage PET值变成空字符串?

我正在try 将一组加载程序函数发送到我的路由,它抛出一个错误 Handler is not a funtion in a reactJs application

如何通过 React JS 中的映射将新元素添加到对象数据数组中

如何将我的 ID 值传递给下一个组件?

map 未显示在react 传单中

你如何使用 refs 访问 React 中映射子项的值?