You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中WebSocket触发的函数内数组为何被判定为空?

问题:React中WebSocket回调里useState变量始终为空的原因及解决办法

我在React中用useState定义了messages数组变量,页面里有个无DOM结构的组件专门监听WebSocket事件。当这个组件收到WebSocket事件通知时,messages的长度始终显示为0——但之前明明已经存了多条消息。为什么进入处理函数时这个数组会是空的?

以下是复现该问题的最简代码:

WebSocketNotificationHandler.tsx

import { useEffect } from "react";
import config from "../../../config.json";
import { IChatMessage } from "../../../services/chatService";

interface WebSocketNotificationHandlerProps {
  onReceiveMessages: (newMessages: IChatMessage[]) => void;
}

export let client: WebSocket = new WebSocket(
  config.webSocketAddress,
  config.webSocketProtocol
);

const WebSocketNotificationHandler = (
  props: WebSocketNotificationHandlerProps
) => {
  useEffect(() => {
    if (client) {
      client.onmessage = async (ev) => {
        handleWebSocketMessage(ev);
      }
    }
  }, []);

  const handleWebSocketMessage = async (ev: MessageEvent<string>) => {
    props.onReceiveMessages([]); // 简化处理,实际会传递1条或多条消息
  }

  return (
    <>
    </>
  );
}

export default WebSocketNotificationHandler;

GroupPage.tsx

import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import chatService, { IChatMessage, ICreateMessageResponse } from '../../../services/chatService';
import ChatMessage from '../../atoms/ChatMessage/ChatMessage';
import ChatStagingArea from '../../organisms/ChatStagingArea/ChatStagingArea';
import WebSocketNotificationHandler from '../../organisms/WebSocketNotificationHandler/WebSocketNotificationHandler';

const GroupPage = () => {
  const { groupName } = useParams();

  const [messages, setMessages] = useState<IChatMessage[]>([]);

  const loadGroup = async () => {
    setMessages([]);
    await chatService // REST API获取当前消息,返回长度>0的列表
      .getGroup(groupName)
      .then(data => {
        setMessages([...data.data.messages]);
      });
  }

  useEffect(() => {
    loadGroup();
  }, []);

  const handleMessageSent = (message: ICreateMessageResponse, text: string) => {
    let newMessages = [...messages, { /* 新消息的数据结构 */ }];
    setMessages(newMessages);
  }

  const handleNewMessages = (newMessages: IChatMessage[]) => {
    console.log(messages.length); // 明明页面能看到多条消息,这里却始终显示0?
    // 这里本应把新消息添加到messages数组中
  };

  return (
    <>
      {messages && messages.map(m => <ChatMessage key={m.id} author={m.fullName} date={m.authoredDate} message={m.message} />)}

      <ChatStagingArea groupName={groupName} onSent={(message, text) => handleMessageSent(message, text)} />

      <WebSocketNotificationHandler onReceiveMessages={newMessages => handleNewMessages(newMessages)} />
    </>
  );
}

export default GroupPage;

原因分析

核心问题是闭包捕获了初始状态的messages:

  1. WebSocketNotificationHandler的useEffect依赖为空数组[],只会在组件挂载时执行一次,给client.onmessage绑定了回调函数。
  2. 此时传递给组件的onReceiveMessages回调(即GroupPage里的handleNewMessages)是GroupPage第一次渲染时创建的,它通过闭包捕获了当时的messages——也就是初始的空数组[]。
  3. 后续GroupPage因messages更新重新渲染时,会生成新的handleNewMessages函数,但WebSocketNotificationHandler里的onmessage回调并未更新,依然引用旧的回调函数,所以它访问的始终是初始的空messages。
  4. 额外问题:全局导出的WebSocket客户端会导致组件卸载后无法清理连接,可能引发内存泄漏或多组件实例共享连接的冲突。

解决方案

方案1:使用函数式更新获取最新状态

不要直接在回调里访问messages,改用setMessages的函数式更新形式,这种方式能直接获取到最新的状态值:

const handleNewMessages = (newMessages: IChatMessage[]) => {
  setMessages(prevMessages => {
    console.log(prevMessages.length); // 这里会显示最新的消息长度
    return [...prevMessages, ...newMessages]; // 合并新旧消息
  });
};

方案2:更新WebSocket回调的依赖,确保引用最新处理函数

修改WebSocketNotificationHandler,把props.onReceiveMessages加入useEffect的依赖数组,这样父组件传递的回调更新时,会重新绑定onmessage:

const WebSocketNotificationHandler = (props: WebSocketNotificationHandlerProps) => {
  useEffect(() => {
    const handleMessage = async (ev: MessageEvent<string>) => {
      props.onReceiveMessages([]); // 实际业务中替换为解析后的消息
    };

    if (client) {
      client.onmessage = handleMessage;
    }

    // 组件卸载时清理监听,避免内存泄漏
    return () => {
      if (client) {
        client.onmessage = null;
      }
    };
  }, [props.onReceiveMessages]); // 添加依赖,回调更新时重新绑定

  return null; // 空组件直接返回null更简洁
};

方案3:修复全局WebSocket客户端问题(可选但推荐)

全局导出的WebSocket客户端容易引发多实例冲突和内存泄漏,建议用useRef在组件内部管理连接:

import { useEffect, useRef } from "react";
import config from "../../../config.json";
import { IChatMessage } from "../../../services/chatService";

interface WebSocketNotificationHandlerProps {
  onReceiveMessages: (newMessages: IChatMessage[]) => void;
}

const WebSocketNotificationHandler = (props: WebSocketNotificationHandlerProps) => {
  const clientRef = useRef<WebSocket | null>(null);

  useEffect(() => {
    // 组件挂载时创建独立连接
    clientRef.current = new WebSocket(
      config.webSocketAddress,
      config.webSocketProtocol
    );

    const handleMessage = async (ev: MessageEvent<string>) => {
      props.onReceiveMessages([]);
    };

    clientRef.current.onmessage = handleMessage;

    // 卸载时关闭连接
    return () => {
      if (clientRef.current) {
        clientRef.current.close();
        clientRef.current = null;
      }
    };
  }, [props.onReceiveMessages]);

  return null;
};

export default WebSocketNotificationHandler;

总结

这个问题本质是React闭包特性导致旧回调捕获了旧状态,通过函数式更新或更新回调依赖即可解决。同时规范WebSocket连接的生命周期管理,能避免内存泄漏和多实例冲突问题。

内容的提问来源于stack exchange,提问作者Nicholas Hill

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 22:05:28