【问题标题】:Is this good practice for useEffect async这是 useEffect 异步的好习惯吗
【发布时间】:2021-07-17 10:05:40
【问题描述】:

我正在创建一个简单的天气应用程序来巩固我的反应挂钩知识。使用 useEffect 时,我在使用异步等待函数时不断出现错误。我环顾四周,找到了在使用效果中一次性使用异步函数的方法。

我的问题是我想使用 async/await 函数两次。我使用 navigator.geolocation 来查找当前位置并设置 lat 和 long 状态。然后,一旦它们被设置,运行一个天气 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 上想要的那样工作,因为它只在使用第一个函数设置 lat 和 long 状态时获取 weatherAPI。在 useEffect 加载 WeatherAPI 之前,lat 和 long 仍然设置为空,从而导致错误。 我想知道这是否是解决此问题的正确方法,或者是否存在我尚未发现的未知副作用。

之后还会弹出此警告,我不确定如何处理。

"src/App.js 第 37:6 行:React Hook useEffect 缺少依赖项:'fetchWeatherData'。要么包含它,要么移除依赖数组 react-hooks/exhaustive-deps"

编辑:cmets 要求的完整代码

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;

【问题讨论】:

  • 你能分享完整的代码,这样我们可以做得更好。
  • 传递给useEffect 的函数在其任何依赖项发生更改时运行。所以是的,正确的方法是使用带有空依赖数组的 useEffect 挂钩来启动地理位置,然后使用带有[lat, long] 的挂钩来加载天气数据。我不确定到底是什么问题,主要是因为您删除了很多相关代码。
  • 对不起,我现在添加了完整的代码

标签: javascript reactjs use-effect


【解决方案1】:

latlong 放在两个单独的useState 中会让你失去控制。您最好将它们放在单个 useState 变量中:

const [coordinates, setCoordinates] = useState([]); // [lat, long]

这样,地理定位例程只调用一次设置器,并且依赖于[coordinates]useEffect 钩子总是在正确的时刻以完整的信息触发。

关于在设置坐标之前触发useEffect的危险,你有两种可能:

  • 提供一些默认值初始化钩子
  • useEffect 运行的函数的开头放置一个 if-guard

关于钩子函数内部缺少的依赖,请查看comprehensive answer

【讨论】:

  • 这是一个很好的建议,但并不是具体问题的真正答案。
  • 仍在挖掘它:-)
  • 将纬度和经度放在一个变量中是很好的建议。我这样做了,它更容易理解。谢谢!我现在在 useEffect 运行的函数的开头使用 if 保护。我的主要问题是在 useEffect 中使用 if 守卫是否是一种好习惯。感谢你们所有人,我现在明白了。谢谢!
  • @HunterScottLacefield - 可能会出现复合变量[lat, long, height, etc. etc.] 变得太大的阶段。那时你最好传递给useReducer 钩子。它允许您根据状态的部分触发效果(例如:useEffect(fn, [state.lat]))
  • @HunterScottLacefield - useReducer 的另一个用例是当您意识到有两个不同的useState 时,这两个变量在页面的生命周期中实际上是相关的;例如,正在下载表示过时天气数据的标志和表示天气数据的标志。您将它们都置于单一的useReducer 状态,并且可以通过完全控制来管理它们的相互依赖关系。
【解决方案2】:

出现的第一个问题:您真的需要这些坐标吗?我的意思是,除了将它们传递给 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。

【讨论】:

  • 不,除了将它们用于 fetchWeatherData() 之外,我不需要它们。这似乎可以解决问题。我只是想知道在 useEffect 中使用 if 条件是否像 Chris G 在第一个 cmets 中所做的那样是不好的做法。
  • 一点也不,我也经常这样做。
  • 当你需要使用一些数据并且你不想等待重新渲染时,因为 useState 的 setter 是异步的,使用源数据。
  • 否则,您需要像其他人一样将其分成两个 useEffects 并将 position 设置为依赖项并像您一样添加一个 if 条件。不,这不是坏习惯。
  • @HunterScottLacefield - 当你第一次意识到你需要的效果取决于 lat 的变化时,你最好传递给 useReducer。你可能会写出像useEffect(fn, [coordinates[0]]) 这样的效果,但它们变得更难理解了。
【解决方案3】:

如果我是你,我会像下面这样重构代码:


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 />

【讨论】:

    【解决方案4】:

    可以在 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>
      );
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-22
      • 1970-01-01
      相关资源
      最近更新 更多