React状态渲染值正确,但组件函数中仍为初始值问题
问题原因:React闭包陷阱
这是典型的React闭包陷阱导致的问题:
组件初始渲染时,requestToken函数捕获了当时的deviceInfo状态(也就是初始值{id: null, info: null})。后续即使通过setDeviceInfo更新了状态,只要requestToken没有重新创建,它内部引用的依然是最初捕获的旧状态值。
解决方案
方案1:用useRef同步状态值
useRef的.current属性是可变的,不受闭包限制,可以用来同步最新的状态值:
const [deviceInfo, setDeviceInfo] = useState({id: null, info: null}); const deviceInfoRef = useRef(deviceInfo); // 同步state到ref,确保ref始终持有最新值 useEffect(() => { deviceInfoRef.current = deviceInfo; }, [deviceInfo]); const requestToken = async (device_auth_res) => { // ... catch (err) { console.error(err.status + ' ' + err.message); const type = err.response.body['error']; if (type === 'authorization_pending') { console.error('Authorization pending...') } if (type === 'expired_token') { console.error(err.response.body.error_description); await requestOauth(deviceInfoRef.current); // 用ref获取最新值 } if (type === 'auth_request_not_found') { console.error(err.response.body.error_description); await requestOauth(deviceInfoRef.current); } } }
方案2:用useCallback让函数跟随状态更新
把requestToken用useCallback包裹,并将deviceInfo加入依赖项,这样每次状态更新时,函数都会重新创建,捕获最新的state:
const requestToken = useCallback(async (device_auth_res) => { // ... catch (err) { console.error(err.status + ' ' + err.message); const type = err.response.body['error']; if (type === 'authorization_pending') { console.error('Authorization pending...') } if (type === 'expired_token') { console.error(err.response.body.error_description); await requestOauth(deviceInfo); } if (type === 'auth_request_not_found') { console.error(err.response.body.error_description); await requestOauth(deviceInfo); } } }, [deviceInfo]); // 依赖deviceInfo,状态更新时函数重新生成
注意:如果这个函数需要传递给子组件,建议配合
React.memo优化子组件的重渲染。
方案3:直接传入最新状态作为参数
如果调用requestToken时能拿到最新的deviceInfo,直接将其作为参数传入是最直接的方式:
// 调用requestToken时传入最新的deviceInfo await requestToken(device_auth_res, deviceInfo); // 修改函数接收参数 const requestToken = async (device_auth_res, currentDeviceInfo) => { // ... catch (err) { console.error(err.status + ' ' + err.message); const type = err.response.body['error']; if (type === 'authorization_pending') { console.error('Authorization pending...') } if (type === 'expired_token') { console.error(err.response.body.error_description); await requestOauth(currentDeviceInfo); } if (type === 'auth_request_not_found') { console.error(err.response.body.error_description); await requestOauth(currentDeviceInfo); } } }
额外优化:修复订阅内存泄漏问题
原代码中sub没有用ref存储,可能导致组件卸载时无法正确取消订阅,建议用useRef保存订阅实例:
const [deviceInfo, setDeviceInfo] = useState({id: null, info: null}); const subscribed = useRef(false); const subRef = useRef(null); // 用ref保存订阅实例 useEffect(() => { subRef.current = subscriber.subscribe(device_info => { if (!subscribed.current) { subscribed.current = true; if (device_info.id && device_info.info) { setDeviceInfo({id: device_info.id, info: device_info.info}); oauth(JSON.parse(JSON.stringify(device_info))); } } }); // 组件卸载时取消订阅 return () => { if (subRef.current) { subRef.current.unsubscribe(); } }; }, []);
内容的提问来源于stack exchange,提问作者batt
相关产品推荐
相关产品推荐

