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

React中useEffect清理调用useCallback封装的close函数失效问题

WebSocket会话关闭失败的原理与解决方法

问题原理

你的推测完全正确,核心原因是React useEffect的闭包捕获特性:

  • 组件挂载时,useEffect 依赖为空数组,只会执行一次,此时它捕获的 close 函数是自定义Hook初始化时的版本——此时session状态还未被设置(仍是undefined)。
  • 当connect执行、WebSocket连接建立后,session状态更新,useCallback会因为依赖session变化生成新的close函数,但useEffect的清理函数依然引用着最初的旧close函数,旧函数里的session始终是undefined,所以销毁时调用close会直接输出"no session!",无法关闭连接。

正确处理方式

有两种可靠的解决思路,推荐第二种更简洁的方案:

方案1:更新useEffect的依赖项

让组件的useEffect依赖close函数,这样每当close更新(即session变化)时,useEffect会重新执行,清理函数也会捕获最新的close引用:

export const WebsocketUser = () => {
  const onMessage = (data: string) => {
    // ...处理消息逻辑
  };

  const { connect: open, close } = useWebsocketSession(onMessage);

  useEffect(() => {
    open();
    
    return () => { 
      console.log("attempting to close");
      close();
    };
  }, [close]); // 添加close作为依赖
};

注意:这种方式下,当session更新时,useEffect会先执行上一次的清理函数(旧close),但此时旧session可能已经不存在或关闭,不会产生副作用,最终组件销毁时会调用最新的close。

方案2:用useRef保存WebSocket实例(推荐)

在自定义Hook中使用useRef替代useState存储WebSocket实例,这样close函数不需要依赖session状态,始终能拿到最新的连接实例,同时useCallback的依赖为空,避免不必要的函数重新生成:

export const useWebsocketSession = (onMessage: (data: string) => void) => {
  const sessionRef = useRef<WebSocket | undefined>(); // 用ref存储实例

  const connect = useCallback(() => {
    const ws = new WebSocket(baseURL);

    ws.onopen = () => { /* ... */ };
    ws.onclose = () => { 
      sessionRef.current = undefined; // 关闭后清空ref
      /* ...其他逻辑 */
    };
    ws.onerror = () => { /* ... */ };
    ws.onmessage = (ev: MessageEvent<string>) => onMessage(ev.data);

    sessionRef.current = ws;
  }, [onMessage]); // 添加onMessage作为依赖,避免闭包捕获旧函数

  const close = useCallback(() => {
    const session = sessionRef.current;
    if (!session) {
      console.log("no session!");
      return;
    }

    if (session.readyState === session.OPEN) {
      console.log("closing as normal");
      session.close(1000);
    }
    sessionRef.current = undefined;
  }, []); // 依赖为空,函数引用始终不变

  return { connect, close };
};

此时组件中的useEffect不需要修改,依赖为空数组即可,因为close函数的引用从未变化,且能通过sessionRef拿到最新的WebSocket实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 10:22:50