【问题标题】:Using Hooks API: does React respect setState order?使用 Hooks API:React 是否尊重 setState 顺序?
【发布时间】:2021-11-16 20:14:20
【问题描述】:

我对 React 的了解并不多,但我边走边学。我在学校学习了基础知识,使用类组件(经典 React),但现在我正在研究 Hooks API(主要是因为我发现它更容易学习和管理,尽管似乎涉及更多关于异步行为的技巧) .所以我的问题可能看起来很愚蠢。

我发现 this thread 关于同一主题的 setState 行为,但这是关于类组件的。

在我当前的应用程序中,我尝试使用事件处理程序设置三种不同的状态。似乎最后一个状态是立即设置的,而其他两个状态在更改为实际值之前保持未定义一段时间。我正在使用 React-Native 组件进行移动开发,因此您会在 <SafeAreaView> 等代码中看到 sn-ps。

export default App = () => {
    const [ destLong, setDestLong ] = useState();
    const [ destLat, setDestLat ] = useState();
    const [ startNav, setStartNav ] = useState(false);
    const [ locations, setLocations ] = useState([
        {
            name: 'IKEA',
            long: '-74.00653395444186',
            lat: '40.68324646680103',
        },
        {
            name: 'JFK Intl. Airport',
            long: '-73.78131423688552',
            lat: '40.66710279890186',
        },
        {
          name: 'Microcenter',
          long: '-74.00516039699959',
          lat: '40.67195933297655',
        }
    ]);

    const startNavigation = (goinglong, goinglat) => {
        setDestLong(goinglong);
        setDestLat(goinglat);
        setStartNav(true);
    }

    return (
        <SafeAreaView style={styles.container}>
          { startNav ? 
            <MapView 
              destLong = {destLong}
              destLat = {destLat}
            />
            :
            <View style={styles.buttonContainer}>
              <ScrollView>
                {
                  locations.map((location, i) => {
                    return(
                      <Card
                        style={styles.card}
                        key={i}
                        title={ location.name }
                        iconName="home"
                        iconType="Entypo"
                        description={ location.long + ", " + location.lat }
                        onPress={() => startNavigation(location.long, location.lat)}
                      />
                    );
                  })
                }
              </ScrollView>
            </View>
          }
        </SafeAreaView>
    );
}

const styles = StyleSheet.create({
    container: {
      flex: 1,
    },
    buttonContainer: {
      width: '100%',
      height: '100%',
      justifyContent: 'center',
      alignItems: 'center'
    },
    logo: {
      width: '50%',
      height: '50%',
      resizeMode: 'contain'
    },
    card: {
      marginBottom: 10,
    }
  });

这会引发错误,因为 MapView 期望 destLongdestLat 正确呈现。当我在 startNavigation 函数中进行控制台登录时,它似乎立即将 startNav 的状态更新为 true onPress,但 destLongdestLat 在设置之前保持未定义几个周期。

我尝试过这样的不同方法:

    useEffect(() => {
        setStartNav(true);
    }, [destLong]);

    const startNavigation = (goinglong, goinglat) => {
        setDestLong(goinglong);
        setDestLat(goinglat);
    }

但它只会让应用程序崩溃(我猜是无限循环)。

我也尝试过完全删除 startNav 状态并像这样在 destLong 上呈现 &lt;MapView&gt;

{ destLong ? 
<MapView 
    destLong = {destLong}
    destLat = {destLat}
/>
:
<View style={styles.buttonContainer}>
    ...
</View>
}

但这也不起作用。


这让我想到了这个问题:Hooks API 是尊重 setState 的顺序,还是彼此异步执行?据我了解是后者。但是,您如何处理这种行为?

【问题讨论】:

  • 简短回答,是的,订单保持不变。在对任何状态更新进行排队之前,您是否有一些异步代码?
  • 我确实有一个异步地理定位功能在后台运行,主动获取当前位置并更新不同的状态。但是,为什么 startNav 状态会立即设置,而 destLong 和 destLat 状态在设置前几个周期仍未定义,即使它们被赋予了明确的值?
  • 这很有趣,因为您共享的代码似乎是完全同步的。您在 Expo Snack 中有一个工作示例(或者您可以创建一个吗?)我们可以实时检查和调试?
  • 通过 useState 设置状态实际上是异步的,或者更确切地说,状态更改被排入队列,然后在重新渲染后返回其新值。这意味着无法保证设置状态的顺序。它们将按顺序开火,但它们可能不会按相同的顺序设置。在你的情况下,我会像这样使用 useEffect: useEffect(() => { if(destLong && destLat && !startNav) { setStartNav(true); } }, [destLong, destLat, startNav]); const startNavigation = (goinglong, goinglat) => { setDestLong(goinglong); setDestLat(goinglat); }
  • 什么、在哪里以及如何确定destLongdestLat 状态没有按预期更新?您看到的实际症状是什么?我已经看到并处理了与您类似的其他 SO 问题,它是返回纬度/经度坐标的地理位置数据,在数据进入之前需要几个“滴答”/渲染周期,您就可以开始计算距离/等。

标签: reactjs react-native react-hooks


【解决方案1】:

我也在这里添加我的评论,因为我无法在上面的评论中添加正确的格式。

通过useState 设置状态实际上是异步的,或者更确切地说,状态更改被排入队列,然后在重新渲染后返回其新值。这意味着无法保证设置状态的顺序。它们会按顺序开火,但可能不会按相同顺序设置。

您可以在此处阅读更多信息:https://dev.to/shareef/react-usestate-hook-is-asynchronous-1hia,以及此处https://blog.logrocket.com/a-guide-to-usestate-in-react-ecb9952e406c/#reacthooksupdatestate

在你的情况下,我会像这样使用useStateuseEffect

useEffect(() => {
    if(destLong && destLat && !startNav) {
        setStartNav(true);
    }
}, [destLong, destLat, startNav]);

const startNavigation = (goinglong, goinglat) => {
    setDestLong(goinglong);
    setDestLat(goinglat);
}

话虽如此,我认为您可以通过完全省略 startNav 状态并更新您的条件渲染来进一步简化代码:

{ (destLat && destLong) ? 
    <MapView 
      destLong = {destLong}
      destLat = {destLat}
    />
    :
    <View style={styles.buttonContainer}>
      ...
    </View>
  }

上面应该有相同的效果,因为你有两个未定义的状态,当它们都被定义时,你想渲染一些东西并使用它们的值。

如果您想再次显示选项,您可以通过 setDestLat(undefined)setDestLong(undefined) 再次将状态设置为未定义

【讨论】:

  • 完美!我想了解有关 useEffect 功能的更多信息,这清除了它。您提供的解决方案都非常有效。谢谢! :)
  • 异步处理的,是的,确实它们是......你将它们排入队列,一段时间后 React 处理它们并重新渲染,但排入队列状态更新的顺序保持不变。 React 不会对它们进行排序或重新排序。我仍然怀疑 OP 未共享的异步代码是延迟的原因。我怀疑它也会改变纬度/经度数据,这可以解释为什么状态更新是有序的,但突变发生在后面。您的代码似乎绕过了突变/问题。它可能有效,但解释不正确。
  • 检查这个demo,它将 2 个状态更新排入队列,首先将一个值乘以 2,然后从中减去 1。请注意,从 2 开始,输出为 always [2, 3, 5, 9, 17, 33, ....]。如果状态更新的顺序不确定,则如果顺序颠倒,则输出可能是 [2, 2, 2, 2, 2...],如果顺序每次都更改,则输出可能是其他输出。但是,排队状态更新并非如此。结果状态很容易/直接确定。
  • @DrewReese 问题是我的项目在 React/React-Native 中,但我使用的地图组件是 Mapbox,它只在 iOS 和 Android SDK 中可用,所以没有可行的方法来制作它在 Expo 小吃上运行(需要头文件来链接它)。但是here 是我 github 上的完整代码。第 44 行 Geolocation.getCurrentPosition(success, error, options); 是我遗漏的唯一相关行,我怀疑它也在运行异步。
猜你喜欢
  • 2020-07-09
  • 1970-01-01
  • 2019-11-23
  • 1970-01-01
  • 2012-02-27
  • 1970-01-01
  • 2020-03-13
  • 1970-01-01
  • 2018-10-12
相关资源
最近更新 更多