使用异步调用的 React Native useEffect 导致陈旧状态

React Native useEffect with async call results in stale state

我这里有一个简化的 React 本机应用程序,它可以进行网络调用并在加载时设置一个标志。有一个按钮 onPress 处理程序调用另一个方法 doSomething,根据 vscode.

中的 exhaustive-deps 插件,useCallback 和依赖数组中的两个方法都是正确的

当应用程序加载时,我可以看到 isInitialized 标志设置为 true,但是之后按下按钮显示 doSomething 方法中的标志仍然为 false。在这种情况下,似乎没有根据依赖数组重新生成 useCallback 方法。

import React, {useEffect, useState, useCallback} from 'react';
import { Text, View, TouchableOpacity } from 'react-native';


export default function App() {
  const [isInitialized, setIsInitialized] = useState(false);

  useEffect(() => {
    fetch("http://www.google.com").then(() => setIsInitialized(true) );
  }, []);

  const onPress = useCallback(() => {
    doSomething();
  }, [doSomething]);

  const doSomething = useCallback(() => {
    console.log("doSomething", { isInitialized });
  }, [isInitialized]);
  
  return (
    <View style={{flex:1, justifyContent:"center", alignItems:"center"}}>
      {isInitialized &&
        <Text>Initialized</Text>
      }
      <TouchableOpacity onPress={onPress} style={{padding:30, borderWidth:1}}>
        <Text>Press Me</Text>
      </TouchableOpacity>
    </View>
  );
}

谁能解释一下为什么会这样?请注意,陈旧状态仅在网络调用后设置标志时发生,并且仅在使用 useCallback() 的方法之间发生两次跳跃时发生。如果按钮 onPress 直接设置为 doSomething,则标志正确显示为 true。

我在我的所有代码中都以这种方式使用 useCallback,并且我害怕由于不理解这里发生的事情而在意想不到的地方发现陈旧状态。

函数 doSomethingundefined 当您将它作为依赖项传递给 useCallback 时,因此函数不会改变isInitialized。将 doSomething 的声明移到 onPress 上方。在任何地方使用 useCallback 可能不是 the best idea,但我不知道你的用例,我希望你衡量性能和收获 :)

类似 post . See also the React docs on useCallback.

当您在 useCallback 中封装一个函数时,您是在告诉 React 不要更新该函数,除非其中一个依赖项发生变化。但是,useCallback 中的依赖项更改不会触发组件的重新渲染。由于您的 useEffect 没有依赖项,因此永远不会使用新值重新呈现组件。

您有以下代码:

  useEffect(() => {
    fetch("http://www.google.com").then(() => setIsInitialized(true) );
  }, []);

  const onPress = useCallback(() => {
    doSomething();
  }, [doSomething]);

  const doSomething = useCallback(() => {
    console.log("doSomething", { isInitialized });
  }, [isInitialized]);

这三个函数可以重写为:

  useEffect(() => {
    fetch("http://www.google.com").then(() => setIsInitialized(true) );
  }, []);

  useEffect(() => {
    console.log({ isInitialized }):
  }, [isInitialized]);

  const doSomething = useCallback((isInitialized) => {
    console.log("doSomething", { isInitialized });
  });

这样,doSomething 将始终有一个新值传递给它。然后,您将像这样重写您的 TouchableOpacity:

  <TouchableOpacity onPress={() => doSomething(isInitilized)} style={{padding:30, borderWidth:1}}>
    ...

这样,通过在第二个 useEffect.

中强制重新渲染组件,确保了 isInitialized 的最新值

我不确定您的用例,但 useCallback 应谨慎使用。它的要点是及时冻结一个函数并防止它被重新初始化。这仅在您有一个需要大量重新渲染的组件时才有价值;如果你只做一个 fetch,并且 fetch 不会发生太多,useCallback 会导致比它解决的问题更多的问题。