如何在 React 应用中正确使用 socket.io-client

2025-05-25

如何在 React 应用中正确使用 socket.io-client

背景

以下是我第一篇获得 8000+ 阅读量和 Google SERP 排名第一的博客。然而,它有一些问题,所以我决定重写它。

在本文中,我使用全局socket变量来管理 React 应用中的套接字事件。如下所示:



// service/socket.js
export const socket = socketio.connect(SOCKET_URL);
// elsewhere
import {socket} from "service/socket";


Enter fullscreen mode Exit fullscreen mode

然而,其他开发者在评论中建议使用 React context API。我开始觉得使用全局变量也不符合 React 的做法。(虽然我相信那样也行得通,因为套接字的状态不会改变。)

我更新了之前的文章,但觉得需要更多解释。所以我决定写一篇文章,展示如何使用 React context API 来管理一个全局 Socket 实例。

1. 创建套接字上下文

我们将使用useContext hook 为整个应用程序提供 SocketContext。

在以下位置创建文件context/socket.js



import socketio from "socket.io-client";
import { SOCKET_URL } from "config";

export const socket = socketio.connect(SOCKET_URL);
export const SocketContext = React.createContext();


Enter fullscreen mode Exit fullscreen mode

2. 使用套接字上下文并提供一个值

在项目的根目录或使用套接字的最大范围内添加 SocketContext 提供程序:



import {SocketContext, socket} from 'context/socket';
import Child from 'components/Child';

const App = () => {
  return (
    <SocketContext.Provider value={socket}>
      <Child />
      <Child />
      ...
    </SocketContext.Provider
  );
};


Enter fullscreen mode Exit fullscreen mode

3. 现在你可以在任何子组件中使用套接字

例如,在GrandChild组件中,你可以像这样使用套接字:



import React, {useState, useContext, useCallback, useEffect} from 'react';
import {SocketContext} from 'context/socket';

const GrandChild = ({userId}) => {

  const socket = useContext(SocketContext);

  const [joined, setJoined] = useState(false);

  const handleInviteAccepted = useCallback(() => {
    setJoined(true);
  }, []);

  const handleJoinChat = useCallback(() => {
    socket.emit("SEND_JOIN_REQUEST");
  }, []);


  useEffect(() => {
    // as soon as the component is mounted, do the following tasks:

    // emit USER_ONLINE event
    socket.emit("USER_ONLINE", userId); 

    // subscribe to socket events
    socket.on("JOIN_REQUEST_ACCEPTED", handleInviteAccepted); 

    return () => {
      // before the component is destroyed
      // unbind all event handlers used in this component
      socket.off("JOIN_REQUEST_ACCEPTED", handleInviteAccepted);
    };
  }, [socket, userId, handleInviteAccepted]);

  return (
    <div>
      { joined ? (
        <p>Click the button to send a request to join chat!</p>
      ) : (
        <p>Congratulations! You are accepted to join chat!</p>
      ) }
      <button onClick={handleJoinChat}>
        Join Chat
      </button>
    </div>
  );
};


Enter fullscreen mode Exit fullscreen mode

好的,这里有一些解释:

什么是useContext

  • useContext提供一种使用全局状态的 React 方法
  • 您可以在任何子组件中使用上下文
  • 上下文值是状态。React 会注意到它们的变化并触发重新渲染。

这是啥useCallback?为什么把所有处理程序都放进去useCallback

  • useCallback防止在状态更新时重新分配
  • 仅当第二个参数中的元素更新时,函数才会重新分配
  • 由于我们将一个空数组传递给第二个参数,因此函数仅被分配一次
  • 你可能会忘记(或者懒得)使用useCallback。但是,如果你的项目中有很多状态和组件,你可能会面临严重的性能问题

作为第二个参数提供的数组useEffect什么?[socket]

  • 第二个参数称为依赖项数组。React 将监视依赖项数组元素,每当其中一个元素更新时,就会执行第一个参数函数。

  • 如果省略依赖数组useEffect,则只要有状态更新,就会执行该函数。

  • 如果依赖数组为空数组,则该函数将仅执行一次。

  • 在 React 函数组件中,你可以按照以下方式编写componentDidMount和替代:componentWillUnmount



useEffect(() => {
  // here is componentDidMount
  return () => {
    // here is componentWillUnmount
  }
}, []);


Enter fullscreen mode Exit fullscreen mode
  • 强烈建议将第一个参数函数中使用的每个状态放入依赖数组中。

奖金

如果您想使用 JWT 令牌来验证套接字连接,您可以执行以下操作:



const getSocket = () => {
  const token = getAuthToken(); // get jwt token from local storage or cookie
  if (token) {
    return socketio.connect(SOCKET_URL, {
      query: { token }
    });
  }
  return socketio.connect(SOCKET_URL);
};


Enter fullscreen mode Exit fullscreen mode

然后在套接字服务器中,您可以像下面这样获取jwt令牌:



import SocketIO from "socket.io";

const io = new SocketIO.Server(expressApp);
const jwtMiddleware = (socket, next) => {
  const {token} = socket.handshake.query;
  // verify token
};

io.use(jwtMiddleware);


Enter fullscreen mode Exit fullscreen mode
文章来源:https://dev.to/bravemaster619/how-to-use-socket-io-client- Correctly-in-react-app-o65
PREV
她会编码!
NEXT
什么是动态规划(Python 示例)背包问题