首页 > 解决方案 > 基于客户端 Socket.IO 响应的 React/Redux 全局状态管理?

问题描述

我有一个具有多种功能的网络应用程序,例如私人消息传递、购买、优惠等。我想让它实时工作,所以我决定使用 socket.io。我使用 redux 进行全局状态管理,但我不知道如何将它与 socket.IO 结合使用。这是我的想法:

1.使用导出函数创建一个用于套接字处理的文件到 App.js 以创建套接字连接,发送和侦听不同的数据。

2.每当我得到相关的东西,例如通知或购买请求时,我都会更新我的 redux 状态。

3.最后,在我的组件中,我将对那些全局 redux 状态使用 useEffect,如果它发生变化,我将根据我更改的状态重新渲染我的组件。

这是一个好方法吗?如果不是,哪种方法是根据套接字接收到的信息全局管理我的组件的正确方法?

标签: reactjsreduxsocket.io

解决方案


一般来说,根据您的需要,我认为这种方法没有任何问题。我将在这里提供一个可操作的示例。我的示例将假设 TypeScript,因为它比其他方式更容易转换为 JavaScript(如果您不使用 TypeScript)。

关于您的第一个问题,我建议建立 Websocket 连接并将其作为上下文传递,因为您在应用程序的任何地方都使用它,并创建自定义挂钩以在任何地方使用该连接:

import React, { createContext, FunctionComponent, ReactNode, useContext, useEffect, useMemo, useState } from 'react';
import io from 'socket.io-client';

export const WebsocketContext = createContext<SocketIOClient.Socket | null>(null);

const WebsocketProvider: FunctionComponent<{ children: ReactNode }> = ({ children }: { children: ReactNode }) => {
  const [connection, setConnection] = useState<SocketIOClient.Socket | null>(null);

  const options: SocketIOClient.ConnectOpts = useMemo(() => ({}), []);

  useEffect(() => {
    try {
      const socketConnection = io(process.env.BASE_URL || '127.0.0.1', options);
      setConnection(socketConnection);
    } catch (err) {
      console.log(err);
    }
  }, [options]);

  return <WebsocketContext.Provider value={connection}>{children}</WebsocketContext.Provider>;
};

export const useWebsocket = (): SocketIOClient.Socket | null => {
  const ctx = useContext(WebsocketContext);
  if (ctx === undefined) {
    throw new Error('useWebsocket can only be used inside WebsocketContext');
  }
  return ctx;
};

export default WebsocketProvider;

上面我们创建了具有类型SocketIOClient.Socket和默认值的上下文null,因为当连接尚未准备好时,我们必须分配默认值。然后我们创建 Websocket 提供者,FunctionComponent它接受子节点并通过钩子保持连接状态,useState最终返回提供者与 Websocket 连接。我还提到SocketIOClient.ConnectOpts,根据您的需要,您可能希望提供连接选项;使用钩子时静态或动态。此外useEffect,钩子将尝试建立连接或引发错误。唯一会重新运行此钩子的依赖项是连接选项,以防它们动态更改。

最后,我们有自定义钩子useWebsocket,我们可以将其导入任何组件并在上下文提供程序中使用。只需使用上下文提供程序包装您的根组件(或任何其他层次结构级别)以提供上下文,如下例所示:

import React, { FunctionComponent } from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import { v4 as uuid } from 'uuid';
import routes from './App.routes';
import WebsocketProvider from './websocket.context';

const App: FunctionComponent = () => {
  return (
    <WebsocketProvider>
        <Router>
          <Switch>
            {routes.map((route) => (
              <Route key={uuid()} {...route} />
            ))}
          </Switch>
          <Redirect to='/' />
        </Router>
    </WebsocketProvider>
  );
};

export default App;

关于您的第二个问题,您可以使用“useEffect”钩子在连接发出时做出反应并更新您的 Redux(或其他全局状态管理)存储。在这里,我还使用 Elvis 运算符来检查连接是否还没有准备好(如果它还没有准备好,因为当它准备好时null,挂钩将在连接更改时useEffect重新渲染):socket

import React, { FunctionComponent, useEffect, useState } from 'react';
import { useWebsocket } from './websocket.context';
const Foo: FunctionComponent = () => {
  const dispatch = useDispatch();
  const socket = useWebsocket();

  useEffect(() => {
    socket?.on('myEmitEvent', (data: myEmitData) => {
      dispatch(myStoreAction(data));
    });
    return () => {
      socket?.off('myEmitEvent');
    };
  }, [socket, dispatch]);

  return ...
};

export default Foo;

关于您提到的第三个问题,您可以使用useEffecthook 或更简单useSelector的 hook from react-reduxpackage 它会自动捕获您的状态更改,从而触发对必要元素的重新渲染。

简而言之,您的想法很成功,我希望通过这个简短的可操作示例,您将能够改进适合您的解决方案。


推荐阅读