【问题标题】:State is empty after setState using async call from API (hooks) [duplicate]使用来自 API(挂钩)的异步调用 setState 后状态为空 [重复]
【发布时间】:2020-05-11 22:46:55
【问题描述】:

我正在尝试使用从城市名称返回经度和纬度的地理编码定位器。这应该设置为一个州,并用于在地图上显示城市。我可以很好地获取结果,但是,当我尝试设置状态时,状态为空。

这是地图组件的代码:

import React, { useState, useEffect } from "react";
import MapGL, { GeolocateControl } from "react-map-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { OpenStreetMapProvider } from "leaflet-geosearch";

const Map = (props) => {
  const [long, setLong] = useState(null);
  const [lat, setLat] = useState(null);

  useEffect(() => {
    getCoords();
  }, []);

  const TOKEN =
    "xxx.xxx.xxxx-xxxxx";

  const [viewport, setViewPort] = useState({
    width: "20%",
    height: 400,
    latitude: 6.122498,
    longitude: 80.112597,
    zoom: 12,
  });

  const _onViewportChange = (viewport) =>
    setViewPort({ ...viewport, transitionDuration: 3000 });

  const getCoords = async () => {
    // setup
    const provider = new OpenStreetMapProvider();

    // search
    const results = await provider.search({ query: props.currentCity });
    console.log(results[0].x)
    console.log(results[0].y)
    setLong({ long: results[0].x });
    setLat({ lat: results[0].y });

    console.log(long);
    console.log(lat)
  };
  return (
    <div style={{ margin: "0 auto" }}>
      <MapGL
        {...viewport}
        mapboxApiAccessToken={TOKEN}
        mapStyle="mapbox://styles/mapbox/dark-v8"
        onViewportChange={_onViewportChange}
      ></MapGL>
    </div>
  );
};

export default Map;

我已经预设了一些值作为纬度和经度,并依赖于控制台。日志,因此应用程序不会中断。

    console.log(results[0].x)
    console.log(results[0].y)
    setLong({ long: results[0].x });
    setLat({ lat: results[0].y });

    console.log(long);
    console.log(lat)

前两个日志打印的位置很好。最后两个,打印状态的初始状态,即:null。

城市名称来自道具,它来自不同的组件,它确实可以正常工作。

            <Map currentCity={currentCity}/>

我很确定这很愚蠢,但我一生都无法弄清楚是什么。 该地图是一个地图框,我正在使用传单中的 OpenStreetMapProvider。

我尝试了不同的提供商,但没有成功。我很确定我在异步调用中做错了什么。我认为状态在通话结束之前设置。我尝试过使用 Promise,但没有任何改变。

谢谢。

【问题讨论】:

    标签: javascript reactjs async-await


    【解决方案1】:

    因为它的async 方法:

    setLong({ long: results[0].x }); // <--- This is async
    setLat({ lat: results[0].y }); // <--- This is async
    
    console.log(long); // <--- So,You will not get direct updated value
    console.log(lat) // <--- So,You will not get direct updated value
    

    一旦设置了值,你的 dom 就会被渲染,

    所以别担心,你会在你放置 JSX 的地方得到这些值。


    您可以运行下面的 sn-p 并检查 HTML 和控制台中的输出,希望能消除您的疑虑

    const { useState , useEffect } = React;
    
    const App = () => {
    
      const [lat,setLat] = useState(0);
      const [lang,setLang] = useState(0);
    
      useEffect(() => {
        setTimeout(() => {
          setLat(42.12412)
          setLang(41.1231)
    
          console.log("Lat after setLat: " , lat); // <----- Not updated due to async behaviour
          console.log("Lang after setLang: " , lang); // <----- Not updated due to async behaviour
        },2000);
      },[]);
    
      return (
        <div>
          Lat : {lat} <br/> 
          Lang : {lang}
        </div>
      );
    }
    
    ReactDOM.render(<App />, document.getElementById('react-root'));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    <div id="react-root"></div>

    【讨论】:

    • 这实际上很有意义。感谢您的回答!
    • 虽然很高兴知道渲染是异步发生的,但仅仅解释函数组件中的这种行为是不够的。第一次渲染中的闭包变量永远不会具有第二次渲染中的值。如果异步事情是导致这种情况的全部原因,您可以将 console.log 包装在足够长的 setTimeout 中并查看新值,但您不能。试试setTimeout(() =&gt; console.log(lat), 10000),还是会记录0。
    • 内部 useeffect 将 2 settimeout 与 diff time 并检查会发生什么。希望这能消除您的疑虑。
    • 同意,添加 setTimeouts 来观察行为是一个好主意。我们应该将其添加到您的答案中。你想编辑还是我应该?
    • @NicholasTower 是对的,在the duplicate 中已经很好地解释了。
    【解决方案2】:

    longlat 是本地 consts。他们永远不会改变,这不是setLongsetLat 想要做的。设置状态的目的是告诉 react 重新渲染组件。当新的渲染发生时,将使用新值创建新的局部变量。

    所以一切正常,除了你把你的日志语句放在一个没有用的地方。如果您想查看新值,请将 log 语句放在组件的主体中

      const [long, setLong] = useState(null);
      const [lat, setLat] = useState(null);
      console.log('rendering', lat, long);
    

    【讨论】:

    • @Jannemannen,由于const,它没有任何意义,它不会因为异步行为和执行而更新。即使在反应文档中,他们也在使用 constreactjs.org/docs/hooks-state.html
    • @VivekDoshi 你说得对,设置状态有时是异步的,但无论它是否同步,设置状态都不会也不能从当前渲染更新局部变量。您正在将对组件分类很重要的知识转移到功能组件中,这没有任何区别。在类组件中,this.state 将被替换,因此您可以检查您可能希望更改的内容,但这不是由于异步问题。当函数组件设置状态时,闭包变量不会改变,因此同步与异步无关紧要。
    【解决方案3】:

    这段代码没有问题,实际上状态正在更新。 设置状态是一个异步任务,它会批量更新你的状态,最后更新一次状态。

    按编号执行顺序:

    setLong({ long: results[0].x });  -->(3)
    setLat({ lat: results[0].y });    -->(4)
    console.log(long);                -->(1)
    console.log(lat)                  -->(2)
    

    在这里,您正在更新状态并尝试立即对其进行控制台,这就是您得到 null(初始状态)的原因。 记住 SetLong() 和 SetLate() 是异步函数调用。

    【讨论】:

      猜你喜欢
      • 2020-01-23
      • 2021-02-25
      • 1970-01-01
      • 2021-09-07
      • 2020-12-12
      • 1970-01-01
      • 1970-01-01
      • 2019-11-29
      • 1970-01-01
      相关资源
      最近更新 更多