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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 09:15:22