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

如何让React的useEffect仅在页面加载、刷新时执行,组件更新时不触发

解决方案

核心问题原因

  • 空依赖数组的写法存在闭包陷阱:useEffect内部拿到的config和openSnackbar永远是组件首次渲染时的初始值,后续更新后的值无法被内部逻辑读取,导致刷新时监听器逻辑异常
  • 异步连接逻辑没有中断判断:组件卸载后如果sse.connect才执行完成,后续调用initializeListeners就会触发已卸载组件的状态更新报错

实现方案

用useRef存储动态变化的依赖项,既可以让useEffect只在挂载时执行一次,又能保证内部逻辑始终拿到最新的配置和方法:

import { useEffect, useRef } from 'react';

import { useSnackbar } from '../../context/SnackbarContext';
import { parseRigData } from '../data.utils';
import sse from '../../lib/sse';

export function useRig(config) {
  const { openSnackbar } = useSnackbar();
  // 用ref存储最新的动态依赖,更新时不会触发useEffect执行
  const configRef = useRef(config);
  const openSnackbarRef = useRef(openSnackbar);

  // 每次渲染自动更新ref的值,保证内部逻辑永远读取最新值
  useEffect(() => {
    configRef.current = config;
    openSnackbarRef.current = openSnackbar;
  });

  // 主逻辑仅在组件挂载/卸载时执行一次
  useEffect(() => {
    let isUnmounted = false;

    const createListeners = () => {
      const newListeners = {};
      // 读取ref的最新值,规避闭包问题
      configRef.current.forEach(instance => {
        const { name, handleData, showMessage, type } = instance;

        newListeners[name] = ({ data }) => {
          // eslint-disable-next-line no-console
          console.log('[RIG] received', JSON.parse(data, 0, 2));
          const messageBody = parseRigData(data);
          handleData(messageBody);

          if (showMessage) {
            const message = showMessage(messageBody);
            if (message) openSnackbarRef.current(message, type);
          }
        };
      });

      return newListeners;
    };

    const initializeListeners = listeners => {
      configRef.current.forEach(instance => {
        const { events, name } = instance;
        sse.listenForUserMessage(events, listeners[name]);
      });
    };

    const removeListeners = listeners => {
      configRef.current.forEach(instance => {
        const { events, name } = instance;
        sse.removeListener(events, listeners[name]);
      });
    };

    const createConnection = async (events, listeners) => {
      if (!events.length || isUnmounted) return;
      await sse.connect(events);
      // 异步完成后先判断组件是否已卸载,避免无效执行
      if (isUnmounted) {
        sse.disconnect(events);
        return;
      }
      initializeListeners(listeners);
    };

    function getEvents() {
      const result = [];
      configRef.current.forEach(instance => {
        const { events, config } = instance;
        result.push({
          eventTypes: events,
          config
        });
      });
      return result;
    }

    const events = getEvents();
    const listeners = createListeners();
    createConnection(events, listeners);

    return () => {
      isUnmounted = true;
      removeListeners(listeners);
      if (events.length) sse.disconnect(events);
    };
  }, []);
}

该方案可以满足你的需求:主逻辑仅在页面加载、刷新(组件首次挂载)时执行,组件更新不会重新触发监听器的移除和重加,同时完全规避了闭包陷阱和已卸载组件更新的报错,卸载时的清理逻辑也能正常执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 04:54:03