我正在创建一个简单的天气应用程序来巩固我的反应钩子知识。使用 useEffect 时,我在使用异步等待函数时不断出现错误。我环顾四周,找到了在使用效果中一次使用异步函数的方法。
我的问题是我想使用 async/await 函数两次。我使用 navigator.geolocation 来查找当前位置并在状态中设置纬度和经度。然后,一旦它们被设置,运行一个使用新的纬度和经度状态的天气 api。我在这里找到了多种关于设置状态如何等待下一次渲染的解决方案,因此在下一个 fetchAPI 函数中使用新设置的状态将不起作用。
因此,我想出了这个解决方案。
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState();
useEffect(() => {
fetchLocation(/*uses navigator.geolocation to setLong and setLat*/);
// hacked out a way to not call fetch data until lat and long are set.
if (typeof lat == "number" && typeof long == "number") {
fetchWeatherData();
}
console.log(lat, "<= lat");
console.log(long, "<= long");
}, [lat, long]);
这个解决方案就像我在 localhost 上想要的那样工作,因为它只在使用第一个函数设置纬度和经度状态时获取 weatherAPI。在 useEffect 加载weatherAPI 之前,lat 和long 仍然设置为空,导致错误。 我想知道这是否是解决此问题的正确方法,或者是否存在我尚未发现的未知副作用。
此后也会弹出此警告,我不确定如何处理。
"src/App.js 第 37:6 行:React Hook useEffect 缺少依赖项:'fetchWeatherData'。包括它或删除依赖数组 react-hooks/exhaustive-deps"
编辑:根据评论要求的完整代码
import React, { useState, useEffect } from "react";
import WeatherDisplay from "./weather";
require("dotenv").config();
function App() {
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState();
const fetchLocation = () => {
navigator.geolocation.getCurrentPosition((position) => {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
};
const fetchWeatherData = () => {
fetch(
`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`
)
.then((res) => res.json())
.then((result) => {
setData(result);
console.log(result);
});
};
useEffect(() => {
fetchLocation();
// hacked out a way to not call fetch data until lat and long are set.
if (typeof lat == "number" && typeof long == "number") {
fetchWeatherData();
}
console.log(lat, "<= lat");
console.log(long, "<= long");
}, [lat, long]); // set as empty arrays if locations don't work
return (
<div className="App">
{/* data is the data that was fetched from fetchWeatherData() */}
<WeatherDisplay weatherData={data} />
</div>
);
}
export default App;
答案 0 :(得分:1)
将 lat
和 long
放在两个单独的 useState
中会使您失去控制。你最好把它们放在一个 useState
变量中:
const [coordinates, setCoordinates] = useState([]); // [lat, long]
这样,地理定位例程只调用 setter 一次,而依赖于 useEffect
的 [coordinates]
钩子总是在正确的时刻以完整的信息触发。
关于在设置坐标之前触发 useEffect
的危险,您有两种可能性:
useEffect
运行的函数的开头放置一个 if-guard关于钩子函数内部缺失的依赖,请检查这个comprehensive answer
答案 1 :(得分:1)
出现的第一个问题:你真的需要这些坐标吗?我的意思是,除了将它们传递给 fetchWeahterData 之外?
如果没有,为什么要麻烦两个useEffect
?
const fetchWeatherData = (position) => {
fetch(
`${process.env.REACT_APP_API_URL}/weather/?lat=${position.lat}&lon=${position.long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`
)
.then((res) => res.json())
.then((result) => {
setData(result);
console.log(result);
});
};
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
fetchWeatherData(position)
});
}, []);
如果您不传送,则不必设置对 position
的依赖。只需获取一次位置,然后用它调用 fetchWeatherData。
答案 2 :(得分:0)
如果我是你,我会像下面这样重构代码:
const [coords, setCoords] = useState();
// get current position as soon as the component is mounted
useEffect(() => {
navigator.geolocation.getCurrentPosition(res => {
if (res && res.coords) {
setCoords(coords);
}
});
}, []);
// fetch weather data when coords is not empty
useEffect(() => {
if (!coords) {
return;
}
fetchWeatherData(coords);
}, [coords]);
如果您想使用自定义钩子清理您的代码,那么绝对值得一看 this useGeolocation 钩子。
import { useGeolocation } from 'beautiful-react-hooks';
const PositionReporter = () => {
const [geoState, { onChange }] = useGeolocation();
onChange(() => {
console.log('Position changed...');
});
return (
<DisplayDemo>
The current position is:
{geoState.isRetrieving && (<p>Retrieving position...</p>)}
{geoState.isSupported && geoState.position && [
<p key={0}>Lat: {geoState.position.coords.latitude}</p>,
<p key={1}>Lng: {geoState.position.coords.longitude}</p>
]}
</DisplayDemo>
);
};
<PositionReporter />
答案 3 :(得分:0)
可以在 useEffect
中使用内部异步例程,但在这种情况下,您应该在卸载组件时注意清理/取消任务以避免 React 泄漏警告。
使用自定义钩子 (Live sandbox) 的工作演示:
import React, { useState } from "react";
import {
useAsyncEffect,
E_REASON_UNMOUNTED,
CanceledError
} from "use-async-effect2";
import cpFetch from "cp-fetch";
const API_KEY = "YOUR API KEY"; // <------Change this
const getCurrentPosition = (options) => {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, options);
});
};
export default function TestComponent(props) {
const [text, setText] = useState("");
const cancel = useAsyncEffect(function* () {
try {
setText("requesting coords...");
const {
coords: { latitude, longitude }
} = yield getCurrentPosition();
setText(`${latitude} : ${longitude}`);
const response = yield cpFetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}`
).timeout(props.timeout);
setText(JSON.stringify(yield response.json(), null, 2));
} catch (err) {
CanceledError.rethrow(err, E_REASON_UNMOUNTED);
setText(`Failed: ${err.toString()}`);
}
});
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>{text}</div>
<button className="btn btn-warning" onClick={cancel}>
Cancel request
</button>
</div>
);
}