使用 AsyncStorage 登录后,React Native React 导航屏幕不会重定向

React Native React Navigation screen does not redirect after login with AsyncStorage

所以我用 React Native、React Navigation 和 AsyncStorage 构建了一个登录系统。如果用户单击一个按钮,他就会登录,并且 AsyncStorage 键“@loginuser”的值会被刷新。现在我期望 屏幕自动刷新,但我必须关闭应用程序并重新启动它 - 这不是最佳选择。

(我也看到了React-Native/React navigation redirection after login with reduxpost,但是很老了)

App.js

import React from 'react';
import { Text, View, StyleSheet, Button, TouchableOpacity, TextInput } from 'react-native';
import DefaultStackNavigation from './components/Navigation/Navigation';


const App = () => {
  return(
    <View>
      <DefaultStackNavigation />
    </View>
  )
}

export default App;

Navigation.js

import React, {useEffect, useState} from 'react';
//React Native
import { Text, View, StyleSheet} from 'react-native';
//Screens
import HomeScreen from '../HomeScreen/HomeScreen'
import AddScreen from "../AddScreen/AddScreen";
import NotificationScreen from "../NotificationScreen/NotificationScreen";
import MenuScreen from "../MenuScreen/MenuScreen";
import SearchScreen from "../SearchScreen/SearchScreen";
import PostJobScreen from "../PostJobScreen/PostJobScreen";
import JobOfferScreen from "../JobOfferScreen/JobOfferScreen";
import ProfileScreen from "../ProfileScreen/ProfileScreen";
import NoneLoggedinScreen from "../NoneLoggedinScreen/NoneLoggedinScreen"
import SignupModal from "../NoneLoggedinScreen/SignupModal"
//React Navigation
import { createStackNavigator } from "@react-navigation/stack";
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
//Third Party
import AsyncStorage from '@react-native-async-storage/async-storage';




const Tab = createBottomTabNavigator();

//Tab bar
const HomeTabs = () => {
  return (
    <Tab.Navigator tabBarOptions={{style:{height: 50}}, {showIcon: true}, {showLabel: false}} >
      <Tab.Screen name="Home" component={HomeScreen}/>
      <Tab.Screen name="SearchScreen" component={SearchJobStack}/>
      <Tab.Screen name="AddScreen" component={AddScreen}/>
      <Tab.Screen name="NotificationScreen" component={NotificationScreen} />}}/>
      <Tab.Screen name="MenuScreen" component={MenuScreen}/>}}/>
    </Tab.Navigator>
  );
}

const Stack = createStackNavigator();
const STORAGE_KEY = '@loginStatus'

const DefaultStackNavigation = () => {

  const [loginStatus, setLoginStatus] = useState()
  const readData = async () => {
    try {
      const isLoggedIn = JSON.parse(await AsyncStorage.getItem(STORAGE_KEY))
      console.log(isLoggedIn)
      if (isLoggedIn !== null) {
        setLoginStatus(isLoggedIn)
      }
    } catch (e) {
      alert('Failed to fetch the data from storage')
    }
  }

  readData()

  return loginStatus ? (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{headerShown: false}} independent={false}>
        <Stack.Screen name="HomeTabs" component={HomeTabs} />
        <Stack.Screen name="PostJobScreen" component={PostJobScreen} />
        <Stack.Screen name="ProfileScreen" component={ProfileScreen}/>
      </Stack.Navigator>
    </NavigationContainer>
  ) : (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{headerShown: false}} independent={false}>
        <Stack.Screen name="NoneLoggedinScreen" component={NoneLoggedinScreen} />
        <Stack.Screen name="SignupModal" component={SignupModal} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};


export default DefaultStackNavigation;

NoneLoggedinScreen.js

import React, { useState, useEffect } from 'react';
import { Text, View, Button} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEY = '@loginStatus'

const SignupModal = () => {

  const [loginStatus, setLoginStatus] = useState(false)
  const saveData = async (parmLoginStatus) => {
    try {
      await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(parmLoginStatus))
      alert('Data successfully saved -> Logged In')
      console.log(loginStatus)
    } catch (e) {
      alert('Failed to save the data to the storage')
    }
  }

  const onSubmitLogin = () => {
    setLoginStatus(true)
    saveData(true)
  }


  return(
    <View>
      <Text>Login Page. Press the button to log in and stay logged in</Text>
      <Button title="Log in" onPress={() => onSubmitLogin()}/>
    </View>
  );
};

const styles = StyleSheet.create({
  App: {
    flex: 1,
    backgroundColor: "white"
  },
});

export default SignupModal;

现在我期待页面重新加载并且我可以使用该应用程序。不幸的是它没有。数据已保存,登录状态设置为true,但我必须重新启动应用程序才能使用它。背信弃义的是,如果我将 NoneLoggedinScreen.js 文件中的 AsyncStorage 登录逻辑写入 App.js 文件,该应用程序工作正常 -> 但是,这对我来说不是一个替代方案,因为一般结构我认为该应用程序的构建相对明智。 此外,当用户尝试在登录后使用 navigation.navigate("HomeTabs") 手动(通过按钮)重定向时不起作用,并且我收到一个错误,指出主页不存在,这也是不可理解的,因为现在导航实际上已经设置为已登录。有人遇到过这个问题吗?

顺便说一句,这是我的依赖项

"dependencies": {<br>
  "@react-native-async-storage/async-storage": "^1.15.1",<br>
  "@react-native-community/masked-view": "^0.1.10",<br>
  "@react-navigation/bottom-tabs": "^5.11.7",<br>
  "@react-navigation/native": "^5.9.2",<br>
  "@react-navigation/stack": "^5.14.2",<br>
  "react": "16.13.1",<br>
  "react-native": "0.63.4",<br>
  "react-native-gesture-handler": "^1.10.1",<br>
  "react-native-reanimated": "^1.13.2",<br>
  "react-native-safe-area-context": "^3.1.9",<br>
  "react-native-screens": "^2.17.1",<br>
}

在您的身份验证策略中,有一个隐藏的假设,即当您在 NoneLoggedInScreen 中使用 AsyncStorage 保存数据时,将立即使用 DefaultStackNavigation 中的 readData 函数读取数据。这不会发生的原因是 readData 仅在 DefaultStackNavigation renders/re-renders 时被调用。当其子项之一在本地存储中设置一些数据时,不会发生这种情况。

有很多方法可以解决这个问题。将 redux 与 redux-persist 或其他状态管理和持久性设置一起使用,或使用基于上下文的特定解决方案(例如,参见 Authentication in React Applications 上的这篇 Kent C. Dodds 文章)。


您提到的第二个小问题:

navigation.navigate("HomeTabs") after logging in doesn't work, and I get an error that Home doesn't exist

确实有道理,其背后的原因是你有条件地渲染了两个不同的NavigationContainers。他们不知道彼此的路线,所以你不能在他们之间导航。

要解决此问题,您应该渲染一个 NavigationContainer 并有条件地将 Stack.Navigator 之一渲染为它的子项。

为了顺利地管理身份验证流程,我们应该为导航制作两个独立的导航器。我发现这个工作流程是目前最好的。它工作完美并且很干净。 所以我通常在我的项目中创建两个导航器 AuthNavigator.jsAppNavigator.js,然后使用条件渲染。

因此,您可以做的是在 App.js 所在的位置创建一个名为 navigation 的文件夹。然后在 navigation 文件夹中创建两个名为 AppNavigator.jsAuthNavigator.js.

的文件

你的 AuthNavigator.js 应该是这样的

import React from "react";
import NoneLoggedinScreen from "../NoneLoggedinScreen/NoneLoggedinScreen";
import SignupModal from "../NoneLoggedinScreen/SignupModal";
import { createStackNavigator } from "@react-navigation/stack";

const Stack = createStackNavigator();

const AuthNavigator = () => {
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }} independent={false}>
      <Stack.Screen name="NoneLoggedinScreen" component={NoneLoggedinScreen} />
      <Stack.Screen name="SignupModal" component={SignupModal} />
    </Stack.Navigator>
  );
};

export default AuthNavigator;

你的 AppNavigator.js 应该是这样的

import React from "react";
import HomeScreen from "../HomeScreen/HomeScreen";
import AddScreen from "../AddScreen/AddScreen";
import NotificationScreen from "../NotificationScreen/NotificationScreen";
import MenuScreen from "../MenuScreen/MenuScreen";
import PostJobScreen from "../PostJobScreen/PostJobScreen";
import ProfileScreen from "../ProfileScreen/ProfileScreen";
import { createStackNavigator } from "@react-navigation/stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";

const Tab = createBottomTabNavigator();

//Tab bar
const HomeTabs = () => {
  return (
    <Tab.Navigator
      tabBarOptions={
        ({ style: { height: 50 } }, { showIcon: true }, { showLabel: false })
      }
    >
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="SearchScreen" component={SearchJobStack} />
      <Tab.Screen name="AddScreen" component={AddScreen} />
      <Tab.Screen name="NotificationScreen" component={NotificationScreen} />
      <Tab.Screen name="MenuScreen" component={MenuScreen} />
    </Tab.Navigator>
  );
};

const Stack = createStackNavigator();

const AppNavigator = () => {
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }} independent={false}>
      <Stack.Screen name="HomeTabs" component={HomeTabs} />
      <Stack.Screen name="PostJobScreen" component={PostJobScreen} />
      <Stack.Screen name="ProfileScreen" component={ProfileScreen} />
    </Stack.Navigator>
  );
};

export default AppNavigator;

你的 App.js 应该是这样的

import React, { useState, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';

import AuthStorage from './auth/storage';
import AppNavigator from './navigation/AppNavigator';
import AuthNavigator from './navigation/AuthNavigator';

const App = () => {
  const [loginStatus, setLoginStatus] = useState(false);

  // I am using useEffect hook but I would prefer using `AppLoading` to restore token if it exists
  useEffect(() => {
    readData();
  }, []);

  const readData = async () => {
    try {
      const isLoggedIn = JSON.parse(await AuthStorage.getItem());
      console.log(isLoggedIn);
      if (isLoggedIn !== null) {
        setLoginStatus(isLoggedIn);
      }
    } catch (e) {
      alert('Failed to fetch the data from storage');
    }
  };

  return (
    <NavigationContainer>
      {loginStatus ? <AppNavigator /> : <AuthNavigator />}
    </NavigationContainer>
  );
};

export default App;

在您的 App.js 所在的位置创建一个名为 auth 的文件夹。在其中创建一个名为 storage.js 的文件。现在在 storage.js 中粘贴此代码

import AsyncStorage from '@react-native-async-storage/async-storage';

const AuthToken = '@loginStatus';

const storeItem = async (value) => {
  try {
    await AsyncStorage.setItem(AuthToken, JSON.stringify(value));
    return true;
  } catch (e) {
    return false;
  }
};

const getItem = async () => {
  try {
    const jsonValue = await AsyncStorage.getItem(AuthToken);
    return jsonValue != null ? JSON.parse(jsonValue) : null;
  } catch (e) {
    return null;
  }
};

export default { storeItem, getItem };