React/Redux 基于客户端 Socket.IO 响应的全局状态管理?

React/Redux global state management based on Socket.IO responses on client side?

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

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

2.Whenever 我收到了相关信息,例如通知或购买请求 我更新了我的 redux 状态。

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

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

一般来说,根据您的需要,我认为这种方法没有任何问题。我将在这里提供一个可操作的示例。我的示例将采用 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;

关于你的第三个问题,正如你提到的,你可以使用 useEffect 钩子或更简单的 useSelector 钩子,来自 react-redux 包自动捕获您的状态更改,触发必要元素的重新渲染。

简而言之,您的想法是正确的,我希望通过这个简短的可操作示例,您将能够完善适合您的解决方案。