React Native中useState更新后函数内值未变更问题排查
React Native WebSocket回调读取useState旧值问题解析
问题代码
const [id, setId] = useState(null); const functionOne = (id) => { setId(id); }; const functionTwo = (newId) => { if (id === newId) { console.log('ids are the same'); } else { console.log('ids are not the same'); } }; useEffect(() => { const socket = new WebSocket(url); socket.addEventListener('open', () => { console.log('socket connection opened'); }); socket.addEventListener('message', (msg) => { const data = JSON.parse(msg.data); if (data.eventType === 'one') { functionOne(data.id); } if (data.eventType === 'two') { functionTwo(data.id); } }) socket.addEventListener('close', () => { console.log('socket connection closed'); }); }, []);
场景与问题
通过useState存储ID,服务端通过WebSocket发送两种事件:
- 事件
one:将收到的ID存入useState,已验证状态更新成功 - 事件
two:一分钟后发送,携带与one相同的ID,需要和存储的ID比对
问题:调用functionTwo时,id始终是初始值null,即使状态已更新;用useCallback包裹functionTwo并传入[id]依赖也无法解决。
问题根源
这是典型的闭包陷阱:
useEffect的依赖数组为空,仅在组件首次渲染时创建WebSocket连接,其内部的message回调捕获的是首次渲染时的functionOne和functionTwo- 当
setId更新状态触发组件重新渲染时,会生成新的functionOne和functionTwo(引用最新的id),但WebSocket连接仍使用旧的回调函数,调用的是旧版本的functionTwo,自然读取的是初始的null - 仅用
useCallback包裹functionTwo但未更新WebSocket回调,旧回调依然引用旧的functionTwo,因此无效
解决方法
方法一:更新useEffect依赖并重建WebSocket
通过将id、functionOne、functionTwo加入useEffect依赖数组,确保状态或函数更新时重建WebSocket,同时用useCallback稳定函数引用:
const [id, setId] = useState(null); const functionOne = useCallback((id) => { setId(id); }, []); const functionTwo = useCallback((newId) => { if (id === newId) { console.log('ids are the same'); } else { console.log('ids are not the same'); } }, [id]); useEffect(() => { const socket = new WebSocket(url); socket.addEventListener('open', () => { console.log('socket connection opened'); }); socket.addEventListener('message', (msg) => { const data = JSON.parse(msg.data); if (data.eventType === 'one') { functionOne(data.id); } if (data.eventType === 'two') { functionTwo(data.id); } }) socket.addEventListener('close', () => { console.log('socket connection closed'); }); // 组件卸载或依赖变化时关闭旧连接 return () => { socket.close(); }; }, [id, functionOne, functionTwo]);
方法二:用useRef保存最新状态
通过useRef存储id的最新值,避免闭包捕获旧值:
const [id, setId] = useState(null); const idRef = useRef(id); // 状态更新时同步更新ref useEffect(() => { idRef.current = id; }, [id]); const functionOne = (id) => { setId(id); }; const functionTwo = (newId) => { // 读取ref的current属性获取最新id if (idRef.current === newId) { console.log('ids are the same'); } else { console.log('ids are not the same'); } }; useEffect(() => { const socket = new WebSocket(url); // ... 事件监听代码(无需修改) }, []);
方法三:全局状态管理(如Redux)
将id存储到全局状态(如Redux)中,函数直接从全局获取最新值,不受组件渲染闭包限制,这也是用户最终采用的方案:通过自定义Hook模拟useState,将值存入Redux后问题解决。
内容的提问来源于stack exchange,提问作者Sam Leurs
相关产品推荐
相关产品推荐

