React Navigation 5,登录后阻止返回导航

React Navigation 5, block back navigation after login

我在一个项目中使用 React Navigation 5,我在尝试阻止用户在某个点后返回导航时遇到了问题。

该应用使用类似于此的嵌套导航结构:

ROOT (STACK)
|-- LoginScreens (STACK - options={{ gestureEnabled: false }} )
|   |-- Login (SCREEN) -> when successful navigate to "Home"
|   +-- Register (SCREEN) -> after registration, navigate to "Login"
|
+-- Home (TABS - options={{ gestureEnabled: false }} )
    |-- BlahBlah (SCREEN)
    |-- MyProfile (SCREEN)
    +-- Dashboard (TABS)
        |-- AllTasks (SCREEN)
        +-- SomethingElse (SCREEN)

用户登录成功后,用户将被转到 Home 屏幕并且应该无法导航回 LoginScreens 屏幕。

我尝试在 Home 上使用 componentDidMount 生命周期方法,以及 useFocusEffect 挂钩,具有以下内容:

Aaaaand...我 运行 没主意了。有人有什么建议吗?

似乎 React Navigation 的文档试图通过本指南涵盖此用例:

https://reactnavigation.org/docs/en/auth-flow.html

那里的例子非常棘手,已经介绍了状态管理库、reducer、React hooks 以及其他任何没有真正帮助的东西。但是,该指南的摘要是:Conditionally render routes.

取消链接React Navigation 4 及之前的版本,在React Navigation 5 中你可以有条件地渲染路线。这样做可以有效地排除导航到不存在的路线的任何可能性。下面是一个非常简短的示例,说明如何使用简单的状态变量来完成此操作。但是请记住,此示例仅考虑一次呈现一条路线的导航器。如果除了本示例中的路线之外,您还有更多路线呈现,您可能需要调整 RootStack.Navigator 的道具(例如 initialRouteName),或明确导航到特定路线。

import React from "react";
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

import LoginNav from "./screens/LoginNav";
import HomeScreens from "./screens/HomeScreens";

const RootStack = createStackNavigator();

export default class MyApp extends React.Component {
    constructor(props){
        super(props);

        this.state = { isLoggedIn: false };
    }

    setIsLoggedIn = (isLoggedIn)=>{ this.setState({ isLoggedIn }); }

    render = () => {
        // Using an arrow function to allow to pass setIsLoggedIn to LoginNav
        // Pass setIsLoggedIn from the props of LoginNav to the screens it contains
        // then from the screens call this function with a true/false param
        const LoginScreens = (props)=> <LoginNav {...props} setIsLoggedIn={this.setIsLoggedIn} />

        return <NavigationContainer style={{ flex: 1 }}>
            <RootStack.Navigator>
                {(this.state.isLoggedIn === false)
                    // If not logged in, the user will be shown this route
                    ? <RootStack.Screen name="LoginScreens" component={LoginScreens} />
                    // When logged in, the user will be shown this route
                    : <RootStack.Screen name="Home" component={HomeScreens} />
                }
            </RootStack.Navigator>
        </NavigationContainer>;
    }
}

在此示例中,调用 (this.) props.setIsLoggedIn(true) 以呈现 Home 路由,或使用 false 参数调用 return 到 LoginScreens路线。

希望这个例子比文档中的例子更容易理解。

好吧,我不得不承认,要找到 v5 的新重置方法的语法并不容易,老兄...ReactNavigation 文档确实需要站内搜索功能。

总之,reset method可以用,对我来说效果很好。

它看起来像:

import { CommonActions } from '@react-navigation/native';

navigation.dispatch(
  CommonActions.reset({
    index: 0,
    routes: [
      {
        name: 'Home',
        params: { user: 'jane' },
      },
    ],
  })
);

我制作了一个辅助函数,我在我的应用程序的多个地方使用它,看起来像:

import { CommonActions } from '@react-navigation/native';

export const resetStackAndNavigate = (navigation, path) => {
  navigation.dispatch(CommonActions.reset({ index: 0, routes: [{ name: path }] }));
};

我已经为 react-navigation v5 这样做了:

我创建了一个 CustomDrawerContent-Component 来处理对一个项目的每次按下:

(注:无视headerfooter 属性,只是我抽屉的调整。)

...
import {
  DrawerContentScrollView,
  DrawerItem,
} from '@react-navigation/drawer';
...

function CustomDrawerContent(props) {
  const {
    state: {routes, index},
    descriptors,
    navigation,
    header,
    footer,
  } = props;
  return (
    <>
      {header}
      <DrawerContentScrollView {...props}>
        {routes.map((route, i) => {
          const focused = i === index;
          const {title, drawerLabel, drawerIcon} = descriptors[
            route.key
          ].options;

          return (
            <DrawerItem
              key={route.key}
              label={
                drawerLabel !== undefined
                  ? drawerLabel
                  : title !== undefined
                  ? title
                  : route.name
              }
              icon={drawerIcon}
              focused={focused}
              onPress={() => {
                navigation.dispatch(
                  CommonActions.reset({index: i, routes: [{name: route.name}]}),
                  // NOTICE: Removes the routes.<name>.state of the Stack to discard
                  // navigation-Position if coming back to it via Drawer-Menu.
                  // If this Stack-State in seeded later on, you can adjust it here needed
                );
              }}
            />
          );
        })}
      </DrawerContentScrollView>
      {footer}
    </>
  );
}

function MainDrawer(props) {
  const {
    screen,
    screen: {initialRoute},
    navigatorProps,
    header,
    footer,
    hideDrawerItems,
  } = props;
  return (
    <Navigator
      initialRouteName={initialRoute}
      {...navigatorProps}
      drawerContent={(drawerProps) => (
        <CustomDrawerContent {...drawerProps} header={header} footer={footer} />
      )}>
      {createMenuEntries(screen, hideDrawerItems)} // that's  only an custom implementation of mine to create <Screen>-Entries. Feel free to replace it with your own
    </Navigator>
  );
}

export default MainDrawer;

魔术至少在这里:

{routes.map((route, i) => {
...
onPress => navigation.dispatch => CommonActions.reset({index: ⇒⇒ i ⇐⇐

当我们映射每条路线时,如果我们点击它,我们会使用当前索引和路线名称(抽屉项目本身的)重置它的路线状态。

这对我来说非常好用,因为即使你在 News ⇒ News Detail" 中打开抽屉并再次点击 News,你也会被传送到 [=16] 的第一个屏幕=].

我喜欢公认的解决方案,但另一种方法是使用 React Context。

const AuthContext = React.createContext();

const setIsLoggedIn = (isLoggedIn) => {
    this.setState({ isLoggedIn }); 
} 

然后包裹整个导航器:

 <AuthContext.Provider value={setIsLoggedIn}>
     <RootStackNavigator>
        // your screens, etc.
     </RootStackNavigator>
 </AuthContext.Provider>

然后在您的屏幕中,您可以使用:

 const { setIsLoggedIn } = React.useContext(AuthContext);

并在需要时调用它。

查看本指南:https://reactnavigation.org/docs/auth-flow/