在 useEffect API 调用之前调用 useState 变量

Posted

技术标签:

【中文标题】在 useEffect API 调用之前调用 useState 变量【英文标题】:useState variable is called before useEffect API call 【发布时间】:2021-10-02 17:28:43 【问题描述】:

据我了解,useEffect 钩子最后作为 sideEffect 运行。我正在尝试控制台日志 data.main.temp。我可以理解它还不知道那是什么,因为它正在从后面运行的 useEffect 钩子中的 api 中获取数据。

在调用 api 之后,我如何能够访问或控制台日志 data.main.temp? (感觉setTimout是作弊方式?)

import React,  useState, useEffect  from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";

export default function Weather() 
  //State Management//
  const [lat, setLat] = useState([]);
  const [long, setLong] = useState([]);
  const [data, setData] = useState([]);

  //openWeather API key
  const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";

  useEffect(() => 
    const fetchData = async () => 
      //get coordinates//
      navigator.geolocation.getCurrentPosition(function (position) 
        setLat(position.coords.latitude);
        setLong(position.coords.longitude);
      );
      //fetch openWeather api//
      await fetch(`https://api.openweathermap.org/data/2.5/weather/?lat=$lat&lon=$long&units=metric&APPID=$key`)
        .then((res) => res.json())
        .then((result) => 
          setData(result);
          console.log(result);
        );
    ;
    fetchData();
  , [lat, long]);


//Examples of what I want, they run too early before api//
console.log(data.main.temp);
const Farenheit = data.main.temp * 1.8 + 32;

  return (
    <Card>
      typeof data.main != "undefined" ? (
        <div className=`$styles.weatherContainer $styles.clouds`>
          <h2>Weather</h2>
          <p>data.name</p>
          <p>data.main.temp * 1.8 + 32 &deg;F</p>
          <p>data.weather[0].description</p>
          <hr></hr>
          <h2>Date</h2>
          <p>moment().format("dddd")</p>
          <p>moment().format("LL")</p>
        </div>
      ) : (
        <div></div>
      )
    </Card>
  );

【问题讨论】:

【参考方案1】:

你是对的,效果函数在第一次渲染之后运行,这意味着你需要以某种方式等待,直到你的 api 调用完成。这样做的一种常见方法是引入另一个状态标志,指示数据是否可用。

另一件不遵循 react 良好实践的事情是,你的效果函数不止做一件事。

我还添加了简单的错误处理并清理了混合的承诺和异步等待

这是你的重构代码

import React,  useState, useEffect  from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";

//openWeather API key
const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";

export default function Weather() 
  //State Management//
  const [lat, setLat] = useState();
  const [long, setLong] = useState();
  const [data, setData] = useState();
  const [error, setError] = useState();
  const [loading, setLoading] = useState(false);

  useEffect(() => 
    navigator.geolocation.getCurrentPosition((position) => 
      setLat(position.coords.latitude);
      setLong(position.coords.longitude);
    );
  , []);

  useEffect(() => 
    const fetchData = async () => 
      if (lat && long && key) 
        try 
          setLoading(true);
          const response = await fetch(
            `https://api.openweathermap.org/data/2.5/weather/?lat=$lat&lon=$long&units=metric&APPID=$key`
          );
          const data = await response.json();
          setData(data);
          setLoading(false);
         catch (err) 
          setError(err);
          setLoading(false);
        
      
    ;
    fetchData();
  , [lat, long]);

  if (error) 
    return <div>some error occurred...</div>;
  

  return (
    <Card>
      loading || !data ? (
        <div>loading...</div>
      ) : (
        <div className=`$styles.weatherContainer $styles.clouds`>
          <h2>Weather</h2>
          <p>data.name</p>
          <p>data.main.temp * 1.8 + 32 &deg;F</p>
          <p>data.weather[0].description</p>
          <hr></hr>
          <h2>Date</h2>
          <p>moment().format("dddd")</p>
          <p>moment().format("LL")</p>
        </div>
      )
    </Card>
  );



【讨论】:

我明白了,非常彻底。我很感激花时间重构代码,我不知道如何使用单独的 useEffects! 有一个问题,如果我是对的,第一个 useEffect 只会运行一次,而且由于用户的 lcoation 可能会改变,我不需要一个数组依赖项吗?

Math.round(parseFloat(data.main.temp) * 1.8 + 32) °F

顺便说一句,如果有人需要在这里转换为 Farenhiet去
是的,空的依赖数组意味着效果只会运行一次(挂载时)。 getCurrentPosition API 是一种“一次性”操作,因此将其放入“onMount”挂钩是有意义的。但是,如果您想查看用户位置,则需要在钩子内使用navigator.geolocation.watchPosition API。这个 API 让你订阅位置变化。但需要注意的重要一点是,您必须使用效果清理(从效果返回的函数)才能取消注册处理程序return () =&gt; navigator.geolocation.clearWatch(id);【参考方案2】:

你可以使用另一个useEffect,这取决于改变数据状态

useEfect(() => 
  if (data) 
    // do something with data
  
, [data])

【讨论】:

【参考方案3】:

您可以创建一个简单的函数并在您的 API 调用响应中调用它,并直接从 api 响应中传入 data,这样您就可以在有响应时立即访问数据。

例如

...
.then((result) => 
  setData(result);
  getDataValue(result) // this function will be called when the response comes in and you can use the value for anything
  console.log(result);
);

方法2:

您可以使用 useEffect 挂钩来监控数据状态的变化,这样只要该状态有更新,您就可以使用该值来做任何您想做的事情。这是我不太喜欢的选项。

useEffect(() => 
   //this hook will run whenever data changes, the initial value of data will however be what the initial value of the state is

console.log(data) //initial value = [] , next value => response from API
,[data])

【讨论】:

谢谢!我确实喜欢选项一的概念是的

以上是关于在 useEffect API 调用之前调用 useState 变量的主要内容,如果未能解决你的问题,请参考以下文章

React - 是不是在 useEffect 中调用 API

定期为 Dashboard API 调用运行 useEffect

如何阻止多个重新渲染执行多个 api 调用 useEffect?

React Native UseEffect API 调用

在 UseEffect 中为两个 API 调用设置 Promise

如何在 React 中使用 useEffect 挂钩调用多个不同的 api