State 不会在 react-native 中使用 AsyncStorage 更新其值

State does not update its value using AsyncStorage in react-native

我正在尝试将值保存到异步存储,然后根据异步存储的值结果导航到正确的页面。我可以将数据存储在 AsyncStorage 中,但我的状态不会更新,我必须重新加载应用程序才能更新状态。这是我的代码:

我这里有一个 Welcome/Obnoarding 屏幕。我希望此屏幕只显示给新的应用程序用户。因此,当用户按下继续按钮时,我想将一个值保存到异步存储中,以便他们下次登录时不必再次看到登录页面。这是我的入职页面:

const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
  const { width, height } = Dimensions.get("window");

  const btnText = "Contiunue";
  const title = "Book";
  const subTitle = "Fab";

  let [fontsLoaded] = useFonts({
    PinyonScript_400Regular,
  });

  const continueBtn = async () => {
    try {
      await AsyncStorage.setItem('@viewedOnboarding', 'true');
    } catch (error) {
      console.log('Error @setItem: ', error);
    };
  };

  if (!fontsLoaded) {
    return <Text>...Loading</Text>;
  } else {
    return (
      <View style={containerStyle(height, width).container}>
        <ImageBackground
          resizeMode={"cover"}
          style={styles.image}
          source={require("../assets/model.jpg")}
        >
          <LinearGradient
            colors={["#00000000", "#000000"]}
            style={styles.gradient}
          >
            <View style={styles.container}>
              <View style={styles.logoTextContainer}>
                <Text style={styles.logoText}>{title}</Text>
                <Text style={styles.logoText}>{subTitle}</Text>
              </View>

              <ContinueBtn label={btnText} callback={continueBtn} />
            </View>
          </LinearGradient>
        </ImageBackground>
      </View>
    );
  }
};

在我的 AppNavigator 中,我想决定用户应该看到哪个导航。但是当我按下继续页面时,我的应用程序不会导航到我的 TabsNavigator。它保留在我的入职页面上,但如果我刷新该应用程序,该应用程序将导航到我的选项卡导航器。这是我确定用户应该在哪里的代码,具体取决于他们是新用户还是“旧”用户:

const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
  const { width, height } = Dimensions.get("window");

  const btnText = "Contiunue";
  const title = "Book";
  const subTitle = "Fab";

  let [fontsLoaded] = useFonts({
    PinyonScript_400Regular,
  });

  const continueBtn = async () => {
    try {
      await AsyncStorage.setItem('@viewedOnboarding', 'true');
    } catch (error) {
      console.log('Error @setItem: ', error);
    };
  };

  if (!fontsLoaded) {
    return <Text>...Loading</Text>;
  } else {
    return (
      <View style={containerStyle(height, width).container}>
        <ImageBackground
          resizeMode={"cover"}
          style={styles.image}
          source={require("../assets/model.jpg")}
        >
          <LinearGradient
            colors={["#00000000", "#000000"]}
            style={styles.gradient}
          >
            <View style={styles.container}>
              <View style={styles.logoTextContainer}>
                <Text style={styles.logoText}>{title}</Text>
                <Text style={styles.logoText}>{subTitle}</Text>
              </View>

              <ContinueBtn label={btnText} callback={continueBtn} />
            </View>
          </LinearGradient>
        </ImageBackground>
      </View>
    );
  }
};

在异步存储中设置一个值不会触发 AppNavigator 的重新渲染。因此,如果用户按下 continue button,那么视觉上不会发生任何事情,因为 AppNavigator 的状态没有改变。如果您刷新应用程序,您之前使用 setItem 函数设置的标志将在 AppNavigator 初始渲染时重新加载。这就是刷新应用程序后它起作用的原因。

对于这种问题,我建议你使用Context来触发AppNavigator中的状态变化。

这是一个关于其工​​作原理的最小示例。我在代码中添加了注释来指导您。

为了简单起见,我们将做出以下假设:

我们在 Stack 中有两个屏幕,一个是 WelcomeScreen,另一个是 HomeScreen

请注意,我们根据应用程序上下文对屏幕使用条件渲染。您可以添加任何您想要的屏幕,甚至是整个导航器(如果您的导航器是嵌套的,这将是必要的,但模式保持不变)。

应用程序

export const AppContext = React.createContext()

const App = () => {
  // it is important that the initial state is undefined, since
  // we need to wait for the async storage to return its value 
  // before rendering anything
  const [hasViewedOnboarding, setHasViewedOnboarding] = React.useState()

  const appContextValue = useMemo(
    () => ({
      hasViewedOnboarding,
      setHasViewedOnboarding,
    }),
    [hasViewedOnboarding]
  )

  // retrieve the onboarding flag from the async storage in a useEffect
  React.useEffect(() => {
       const init = async () => {
          const value = await AsyncStorage.getItem('@viewedOnboarding')
          setHasViewedOnboarding(value != null ? JSON.parse(value) : false)
       }
       init()
  }, [])

  // as long as the flag has not been loaded, return null
  if (hasViewedOnboarding === undefined) {
    return null
  }

  // wrap everything in AppContext.Provider an pass the context as a value
  return (
      <AppContext.Provider value={appContextValue}>
        <NavigationContainer>
           <Stack.Navigator>
             {!hasViewedOnboarding ? (
                <Stack.Screen name="Welcome" component={WelcomeScreen} />
              ) : (
                <Stack.Screen
                  name="Home"
                  component={HomeScreen}
                />
              )}}
           </Stack.Navigator>
        </NavigationContainer>
     </AppContext.Provider>
  )
}

现在,在您的 WelcomeScreen 中,您需要在存储异步值后访问上下文并设置状态。

const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
  
  // access the context

  const { setHasViewedOnboarding } = useContext(AppContext)
   
  const { width, height } = Dimensions.get("window");

  const btnText = "Contiunue";
  const title = "Book";
  const subTitle = "Fab";

  let [fontsLoaded] = useFonts({
    PinyonScript_400Regular,
  });

  const continueBtn = async () => {
    try {
      await AsyncStorage.setItem('@viewedOnboarding', 'true');
      setHasViewedOnboarding(true)
    } catch (error) {
      console.log('Error @setItem: ', error);
    };
  };

  if (!fontsLoaded) {
    return <Text>...Loading</Text>;
  } else {
    return (
      <View style={containerStyle(height, width).container}>
        <ImageBackground
          resizeMode={"cover"}
          style={styles.image}
          source={require("../assets/model.jpg")}
        >
          <LinearGradient
            colors={["#00000000", "#000000"]}
            style={styles.gradient}
          >
            <View style={styles.container}>
              <View style={styles.logoTextContainer}>
                <Text style={styles.logoText}>{title}</Text>
                <Text style={styles.logoText}>{subTitle}</Text>
              </View>

              <ContinueBtn label={btnText} callback={continueBtn} />
            </View>
          </LinearGradient>
        </ImageBackground>
      </View>
    );
  }
};