如何让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
相关产品推荐
相关产品推荐

