React监听页面切换时无法获取最新wsSocketIOProvider的问题
问题描述
我通过以下代码绑定visibilitychange事件监听浏览器标签页切换:
const handleVisibilityChange = () => { if (!wsSocketIOProvider) { console.warn("provider is null"); return; } let connected = wsSocketIOProvider?.ws?.connected; if (connected) { console.log("connected is ok"); } else { console.error("disconnected......"); window.location.href = "/doc/tab"; } }; React.useEffect(() => { document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, []);
当前遇到的问题:触发handleVisibilityChange时,即便wsSocketIOProvider已经更新,该变量仍始终为null。
wsSocketIOProvider的定义及初始化逻辑:
const [wsSocketIOProvider, setWsSocketIOProvider] = useState<SocketIOClientProvider>(); const { connState, editorView, texEditorSocketIOWs } = useSelector( (state: AppState) => state.projEditor ); React.useEffect(() => { if (texEditorSocketIOWs) { setWsSocketIOProvider(texEditorSocketIOWs); } }, [texEditorSocketIOWs]);
我曾尝试用useRef保存,但问题依旧:
const socketRef = useRef(wsSocketIOProvider); const handleVisibilityChange = () => { if (!socketRef || !socketRef.current) { console.warn("provider is null"); return; } let connected = socketRef.current?.ws?.connected; if (connected) { console.log("connected is ok"); } else { console.error("disconnected......"); window.location.href = "/doc/tab"; } };
原因分析
- 闭包捕获旧值:初始写法中,
useEffect的依赖数组为空,导致handleVisibilityChange捕获的是组件首次渲染时的wsSocketIOProvider(初始值为null)。后续组件重新渲染时,即便wsSocketIOProvider更新,绑定在document上的还是旧的事件处理函数,自然拿不到最新值。 - useRef未同步更新:你尝试的
useRef写法仅在组件首次渲染时赋值了初始值,后续wsSocketIOProvider更新时,socketRef.current不会自动同步,因此依然无法获取最新值。
解决方法
方法一:更新useEffect依赖,重新绑定事件
将wsSocketIOProvider加入useEffect的依赖数组,每次变量更新时,自动移除旧监听并绑定新的处理函数,确保拿到最新值:
const handleVisibilityChange = () => { if (!wsSocketIOProvider) { console.warn("provider is null"); return; } let connected = wsSocketIOProvider?.ws?.connected; if (connected) { console.log("connected is ok"); } else { console.error("disconnected......"); window.location.href = "/doc/tab"; } }; React.useEffect(() => { document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [wsSocketIOProvider]); // 加入依赖
若担心handleVisibilityChange重复创建导致频繁解绑/绑定,可搭配useCallback优化:
const handleVisibilityChange = React.useCallback(() => { if (!wsSocketIOProvider) { console.warn("provider is null"); return; } let connected = wsSocketIOProvider?.ws?.connected; if (connected) { console.log("connected is ok"); } else { console.error("disconnected......"); window.location.href = "/doc/tab"; } }, [wsSocketIOProvider]); // 依赖wsSocketIOProvider React.useEffect(() => { document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [handleVisibilityChange]); // 依赖useCallback返回的稳定函数
方法二:用useRef同步最新值
新增一个useEffect专门同步wsSocketIOProvider到ref中,确保ref始终持有最新值:
const socketRef = useRef<SocketIOClientProvider>(); // 同步wsSocketIOProvider到ref React.useEffect(() => { socketRef.current = wsSocketIOProvider; }, [wsSocketIOProvider]); const handleVisibilityChange = () => { if (!socketRef.current) { console.warn("provider is null"); return; } let connected = socketRef.current?.ws?.connected; if (connected) { console.log("connected is ok"); } else { console.error("disconnected......"); window.location.href = "/doc/tab"; } }; React.useEffect(() => { document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, []); // 依赖为空,因为handleVisibilityChange读取的是ref的current,不受闭包限制
内容的提问来源于stack exchange,提问作者Dolphin
相关产品推荐
相关产品推荐

