如何在 React.js 应用程序中将 WebSocket 与 Redux 同步

Redux 是一种用于 React 应用程序的流行状态管理库,它可与 Web sockets 配合使用,以实现客户端与服务器之间的实时通信。redux-ws-middleware 是 Redux 的中间件,可以轻松将 WebSocket 集成到 React 和 Redux 应用程序中。在本文中,我们将深入研究 redux-ws-middleware 软件包,了解如何使用它在 Redux 应用程序中建立实时通信。

当应用程序使用 API(WebSockets)进行实时通信时,该库提供了许多有用的工具。

安装

# using npm
npm install redux-ws-middleware

# using yarn
yarn add redux-ws-middleware

一旦安装了该库,就可以将其添加为 Redux 中间件,在应用程序中使用。您只需使用该库创建一个中间件实例,并将您的配置传递给它即可。

示例

// File: "src/store/Socket/socketOptions.ts"

import { Dispatch } from '@reduxjs/toolkit';
import { MiddlewareOptions } from 'redux-ws-middleware';
import { socketMessageHandler } from './socketMessageHandler';

// What client sends to the server (RAW).
type ScoketReq = {
  method: string;
  data: Record<string, unknown>;
};

// What server sends to the client (RAW).
type SocketRes = {
  [method: string]: Record<string, unknown>;
};

// What server expects to receive.
type ScoketSerializedReq = {
  [method: string]: Record<string, unknown>;
};

// What client expects to receive.
type SocketDeserializedRes = Record<string, unknown>;

export const socketOptions: MiddlewareOptions<ScoketReq, SocketRes, ScoketSerializedReq, SocketDeserializedRes> = {
  url: 'ws://localhost:3000',

  // The types that need to be dispatched with the redux dispatch
  // to control the lib.
  //
  // [0] ('/Request$/' in our case) - sends signal to the API.
  // The lib listens to the actions that end with "Request" and
  // sends action payload to the server.
  // [1] ('CONNECT' in our case) - opens the socket connection.
  // [2] ('DISCONNECT' in our case) - closes the socket connection.
  //
  // WARNING: The order matters!
  actionTypes: [/Request$/, 'CONNECT', 'DISCONNECT'],

  // The action types that lib dispatches. These types can be
  // handled in reducers or slices.
  //
  // [0] ('CONNECTED' in our case) - received when the socket
  // connection has been establised.
  // [1] ('DISCONNECTED' in our case) - received when the socket
  // connection has been closed (with additional details in the
  // action payload).
  //
  // WARNING: The order matters!
  completedActionTypes: ['CONNECTED', 'DISCONNECTED'],

  // The lib calls the function when receives the message.
  //
  // "res" is already deserialized response (SocketDeserializedRes).
  // See below how to deserialize the response to client-friendly
  // format (if needed).
  // "dispatch" is a redux dispatch we can call actions with.
  onMessage: (res, dispatch) => {
    // Here you can identify your responses by methods and
    // take them to the reducers/slices by using dispatch.
    return socketMessageHandler(res, dispatch);
  },

  // To avoid infinite loop and spamming the lib tries to
  // reconnect back to the server when the connection gets
  // unnexpecnely closed.
  //
  // In our case first try will be immedialely after the
  // disconnection. If no success then the lib will try in one
  // seccond. If no success - five seconds. And then every next try
  // will be in five secconds after faulure.
  //
  // NOTE: The array size is not limited.
  reconnectionInterval: [0, 1000, 5000],


  // This should be used for the API request serialization.
  // For example: converting payloads from camelCase to snake_case.
  // serialize: (req: ScoketReq) => ScoketSerializedReq
  serialize: ({ method, data }) => ({ [method]: data }),

  // This should be used for the API response deserialization. 
  // For example: converting API responses from snake_case to camelCase.
  // deserialize: (res: SocketRes) => SocketDeserializedRes
  deserialize: (res) => res[Object.keys(res)[0]],

  // NOTE: The lib also takes many other parameters to make
  // everything customized.
  // For more info visit the git repository with the examples.
  //
  // ...
};

现在,让我们将中间件连接到 Redux。下面是一个如何使用 Redux 工具包的示例。

示例:

// File: "src/store/appStore.ts"

import { configureStore } from '@reduxjs/toolkit';
import { createSocketMiddleware } from 'redux-ws-middleware';

import { socketOptions } from './Socket/socketOptions';
import { appReducer } from './appReducer';

export const appStore = configureStore({
  reducer: appReducer,
  middleware: (getDefaultMiddleware) => {
    const middleware = getDefaultMiddleware({ serializableCheck: false });
 
    // Adding our middleware to the redux middleware
    return [...middleware, createSocketMiddleware(socketOptions)];
  }
});

Bonus

我设计了一种方法来处理通信,并将响应发送给特定的 reducer/slice。

示例

// File: "src/store/Socket/socketMessageHandler.ts"

import { Dispatch } from '@reduxjs/toolkit';

import { sessionActions } from '../Session/sessionSlice';
import { todosActions } from '../Todos/todosSlice';


// I found it really easy to map method name
// to the [success, failure] actions.
const socketHandlers = {
  get_session: [
    sessionActions.getSessionFulfilled,
    sessionActions.getSessionRejected
  ],
  create_session: [
    sessionActions.createSessionFulfilled,
    sessionActions.createSessionRejected
  ],
  delete_session: [
    sessionActions.deleteSessionFulfilled,
    sessionActions.deleteSessionRejected
  ],

  get_todos: [
    todosActions.getTodosFullfiled,
    todosActions.getTodosRejected
  ],
  create_todo: [
    todosActions.createTodoFullfiled,
    todosActions.createTodoRejected
  ],
  update_todo: [
    todosActions.updateTodoFullfiled,
    todosActions.updateTodoRejected
  ]
};

export const socketMessageHandler = (res, dispatch: Dispatch) => {
  const { method, result, error } = res;
  if (!socketHandlers[method]) return;

  const [fulfilled, failure] = socketHandlers[method];

  // We need to figure out how can we recognize success
  // and failure responses.
  // My suggestion is having error field which can be
  // message (string) or code (number)
  // The code can be mapped to some mesage on the client side.

  // In my case, I receive the "error" from the API.
  if (error && failure) {
    dispatch(failure(error));
  } else if (!error && fulfilled) {
    dispatch(fulfilled(result));
  }
};

一切准备就绪后,我们可以看看如何向服务器发送信号。要向服务器发送信号,我们需要编写 redux 操作。

示例:

import { createAction } from '@reduxjs/toolkit';

import type { AppThunkAction } from '../appStoreTypes';

// This action is needed just to send the signal to the server.
// The action's type ends with "Request" so the lib will
// recognize it and send to the server once action gets dispatched.
//
// WARNING: This is private action and shouldn't be dispatched from any
// other place except the other actions
export const createSessionRequest = createAction<SubscribeApiReq>('session/createSessionRquest');

// This action should be dispatched from a react component.
export const createSession = (payload: any): AppThunkAction => {

  return (dispatch) => {
    dispatch(createSessionRequest({ method: 'create_sesssion', payload }));
  };
};

详细例子:

GitHub:https://github.com/maxzinchenko/redux-ws-middleware/tree/master/examples

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论