You need to enable JavaScript to run this app. React、Next.js开发WebSocket日志应用时state始终返回初始值
优惠活动
大模型
产品
解决方案
定价
更多

React、Next.js开发WebSocket日志应用时state始终返回初始值

核心问题成因

闭包捕获的过期state引用

useEffect依赖数组为空,意味着该逻辑仅在组件第一次挂载时执行一次。此时绑定到WebSocket实例的onWebSocketMessage回调,捕获到的是首次渲染周期下的固定变量引用——即空数组状态的eventLog、初始值0的updateFlag。
哪怕后续组件因state更新触发重渲染,已经绑定到WebSocket实例上的消息回调永远不会更新内部捕获的变量引用,始终读取第一次渲染时的初始state值。这就是为什么拼接日志时永远只显示单条新消息、console.log(updateFlag)永远输出0的根本原因。

组件作用域普通变量的引用失效

写在组件内部的let socket = null,每次组件重渲染都会被重新初始化为null,和已经创建完成的WebSocket实例完全脱钩。这种写法既会丢失对WebSocket实例的控制,也无法在组件卸载时正确关闭连接,存在内存泄漏风险。

具体修复方案
  • 采用函数式state更新绕开闭包问题
    调用setState时传入回调函数,回调入参是React官方维护的当前最新state值,不依赖外层闭包捕获的旧变量,从根源解决读不到最新state的问题:
    • 替换setEventLog逻辑:不再引用外层的eventLog变量拼接日志,直接基于React传入的最新日志数组做拼接
    • 替换setUpdateFlag逻辑:不再引用外层的updateFlag计算递增量,直接基于React传入的最新标记值计算新值
  • 用useRef持久化WebSocket实例引用
    useRef创建的对象在组件全生命周期内引用固定,不会因重渲染被重置,适合存储WebSocket这类需要跨渲染周期访问的实例。
  • 补充副作用清理逻辑
    在useEffect的返回函数中增加连接关闭逻辑,避免组件卸载、路由切换后旧的WebSocket连接持续接收消息,触发无效state更新。
  • 优化列表渲染key值
    当前使用数组索引index作为日志列表的key,在新日志始终插入数组头部的场景下会出现渲染错位,建议为每条日志绑定唯一标识作为key值。
修复后的参考代码
import React, { useState, useEffect, useRef } from 'react';
import { Container, Row, Col } from 'react-bootstrap';

export default function Home() {
  // 持久化WebSocket实例引用,避免重渲染时被重置
  const socketRef = useRef(null);
  const [eventLog, setEventLog] = useState([]);
  const [updateFlag, setUpdateFlag] = useState(0);

  const onWebSocketMessage = (e) => {
    const data = JSON.parse(e.data);
    const newLogItem = {
      title: data.title,
      icon: data.icon,
      body: data.body,
      date: new Date(),
    };

    // 函数式更新:prevLog为React维护的最新日志数组,不受闭包影响
    setEventLog(prevLog => [newLogItem, ...prevLog]);
    
    // 函数式更新:prevFlag为最新的updateFlag值,基于当前值计算递增量
    setUpdateFlag(prevFlag => {
      // 如需打印最新计数,可在更新回调内读取值
      console.log('已接收消息数:', prevFlag + 1);
      return prevFlag + 1;
    });
  };

  const connectWebSocket = (token) => {
    socketRef.current = new WebSocket('wss://localhost/' + token);
    socketRef.current.onmessage = onWebSocketMessage;
  };

  useEffect(() => {
    const token = localStorage.getItem('token');
    connectWebSocket(token);

    // 组件卸载时清理WebSocket连接
    return () => {
      if (socketRef.current) {
        socketRef.current.onmessage = null;
        socketRef.current.close();
        socketRef.current = null;
      }
    };
  }, []);

  const eventLogHtml = eventLog.map((obj, index) => (
    // 生产环境建议替换为日志项唯一ID作为key
    <div key={index}>
      <h2>{obj.title}</h2>
      <div>
        <img src={obj.icon} style={{ width: '8rem' }} />
        <span>
          <p>{obj.body}</p>
          <p>{obj.date.toString()}</p>
        </span>
      </div>
    </div>
  ));

  return (
    <Container>
      <Row>
        <Col>
          <h1>Event log</h1>
          {eventLogHtml}
        </Col>
      </Row>
    </Container>
  );
}

注意:如果需要追踪updateFlag的实时值做其他业务逻辑,建议单独通过useEffect监听updateFlag的变化执行对应操作,不要在消息回调里直接读取外层的updateFlag变量,否则依然会读到闭包捕获的旧值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.11 10:27:04