这是 useEffect async 的好习惯吗

时间:2021-04-23 07:42:14

标签: javascript reactjs use-effect

我正在创建一个简单的天气应用程序来巩固我的反应钩子知识。使用 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;

4 个答案:

答案 0 :(得分:1)

latlong 放在两个单独的 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>
  );
}
相关问题