React Native天气应用首次渲染时useEffect异步函数失效问题
问题
开发React Native天气应用时,首次渲染通过useEffect触发异步流程:请求位置权限→获取定位→调用天气API。首次获取定位成功,但调用天气API时抛出TypeError: Cannot read property 'coords' of null,二次渲染后API调用正常。
相关代码
// States const [loading, setLoading] = useState(true); const [location, setLocation] = useState(null); const [error, setError] = useState(null); // useEffect useEffect(() => { requestLocationPermission(); }, []); // 请求位置权限 const requestLocationPermission = async () => { console.log('requestLocationPermission started'); try { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, ); if (granted === PermissionsAndroid.RESULTS.GRANTED) { console.log('You can use the location'); getLocation(); } else { console.log('Location permission denied'); } } catch (err) { console.warn(err); } console.log('requestLocationPermission finished'); }; // 获取定位信息 const getLocation = async () => { console.log('getLocation started'); try { await Geolocation.getCurrentPosition(position => { setLocation(position); console.log(position); getWeather(); }); } catch (err) { console.log('Err:' + err); } console.log('getLocation finished'); }; // 调用天气API const getWeather = async () => { console.log('getWeather started'); try { const response = await fetch( `https://api.openweathermap.org/data/2.5/weather?lat=${location['coords']['latitude']}&lon=${location['coords']['longitude']}&appid=${WEATHER_API_KEY}`, ).then(response => response.json()); console.log('response'); console.log(response); } catch (err) { console.log('Err:' + err); } console.log('getWeather finished'); };
首次渲染控制台日志
info Reloading app... BUNDLE ./index.js LOG Running "WeatherApp" with {"rootTag":331} LOG requestLocationPermission started LOG You can use the location LOG getLocation started LOG requestLocationPermission finished LOG getLocation finished LOG {"coords": {"accuracy": 600, "altitude": 0, "heading": 0, "latitude": 37.4220936, "longitude": -122.083922, "speed": 0}, "extras": {"networkLocationType": "cell"}, "mocked": false, "timestamp": 1682430365994} LOG getWeather started LOG Err:TypeError: Cannot read property 'coords' of null LOG getWeather finished
二次渲染控制台日志
info Reloading app... BUNDLE ./index.js LOG Running "WeatherApp" with {"rootTag":331} LOG requestLocationPermission started LOG You can use the location LOG getLocation started LOG requestLocationPermission finished LOG getLocation finished LOG {"coords": {"accuracy": 600, "altitude": 0, "heading": 0, "latitude": 37.4220936, "longitude": -122.083922, "speed": 0}, "extras": {"networkLocationType": "cell"}, "mocked": false, "timestamp": 1682430365994} LOG getWeather started LOG Err:TypeError: Cannot read property 'coords' of null LOG getWeather finished LOG requestLocationPermission started LOG You can use the location LOG getLocation started LOG requestLocationPermission finished LOG getLocation finished LOG {"coords": {"accuracy": 600, "altitude": 0, "heading": 0, "latitude": 37.4220936, "longitude": -122.083922, "speed": 0}, "extras": {"networkLocationType": "cell"}, "mocked": false, "timestamp": 1682432167007} LOG getWeather started LOG response LOG {"base": "stations", "clouds": {"all": 0}, "cod": 200, "coord": {"lat": 37.4221, "lon": -122.0839}, "dt": 1682431628, "id": 5375480, "main": {"feels_like": 281.24, "humidity": 85, "pressure": 1013, "temp": 281.82, "temp_max": 284, "temp_min": 280.34}, "name": "Mountain View", "sys": {"country": "US", "id": 2010364, "sunrise": 1682428825, "sunset": 1682477513, "type": 2}, "timezone": -25200, "visibility": 10000, "weather": [{"description": "mist", "icon": "50d", "id": 701, "main": "Mist"}], "wind": {"deg": 180, "speed": 1.54}} LOG getWeather finished
原因分析
核心问题是React的setState是异步操作:在getCurrentPosition的回调里调用setLocation(position)后,组件并不会立刻更新location状态,此时getWeather函数访问的location还是初始值null,因此触发Cannot read property 'coords' of null错误。
二次渲染时,location已经被更新为有效的定位数据,所以再次调用getWeather时能正常访问coords属性。
解决办法
提供三种可行方案,按需选择:
方案1:直接传递定位数据给getWeather
不需要依赖location状态,直接把getCurrentPosition返回的position作为参数传给getWeather,避免等待状态更新:
const getLocation = async () => { console.log('getLocation started'); try { await Geolocation.getCurrentPosition(position => { setLocation(position); console.log(position); // 直接传入position getWeather(position); }); } catch (err) { console.log('Err:' + err); } console.log('getLocation finished'); }; const getWeather = async (position) => { console.log('getWeather started'); try { // 使用传入的position,而非state中的location const response = await fetch( `https://api.openweathermap.org/data/2.5/weather?lat=${position['coords']['latitude']}&lon=${position['coords']['longitude']}&appid=${WEATHER_API_KEY}`, ).then(response => response.json()); console.log('response'); console.log(response); } catch (err) { console.log('Err:' + err); } console.log('getWeather finished'); };
方案2:通过useEffect监听location变化
新增一个useEffect,当location状态变为有效数据时自动调用getWeather:
// 新增useEffect,监听location变化 useEffect(() => { if (location) { getWeather(); } }, [location]); // 修改getLocation,移除内部的getWeather调用 const getLocation = async () => { console.log('getLocation started'); try { await Geolocation.getCurrentPosition(position => { setLocation(position); console.log(position); // 这里不再调用getWeather }); } catch (err) { console.log('Err:' + err); } console.log('getLocation finished'); };
方案3:在getWeather中增加空值判断
在访问location.coords前先检查location是否存在,避免报错:
const getWeather = async () => { console.log('getWeather started'); // 先判断location是否有效 if (!location) { console.log('Location not available yet'); return; } try { const response = await fetch( `https://api.openweathermap.org/data/2.5/weather?lat=${location['coords']['latitude']}&lon=${location['coords']['longitude']}&appid=${WEATHER_API_KEY}`, ).then(response => response.json()); console.log('response'); console.log(response); } catch (err) { console.log('Err:' + err); } console.log('getWeather finished'); };
内容的提问来源于stack exchange,提问作者mbattaloglu
相关产品推荐
相关产品推荐

