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

cri*_*ian 5 back reactjs react-native react-navigation-v5

我在一个项目中使用 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)
Run Code Online (Sandbox Code Playgroud)

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

我尝试在 上使用componentDidMount生命周期方法Home以及useFocusEffect钩子,如下所示:

  • 将回调放置到 React Native 的BackHandler,从处理程序返回 true 工作(true 表示已处理后退动作,不会调用进一步的后退处理程序),但它也会阻止屏幕中的任何后退导航Home(例如,我无法从到 MyProfile 的仪表板)。
  • 使用navigation.reset({ index: 1, routes: [{ name: "Home" }] }). 如果没有index: 1导航,就会回到 ROOT 的 initialRoute(在本例中为LoginScreens)。使用index: 1Maximum update depth exceeded会引发错误。
  • 而是直接导航到Home,我曾尝试使用navigation.reset()(注:无参数,可以清除整个浏览历史),并导航到后Home屏幕。这并没有达到预期的效果,因为LoginScreens在导航到 之前,当前路线(ROOT 的 initialRoute,在本例中为:)仍被推送到导航历史记录中Home
  • 以不同的方式结合导航和重置调用,我只是设法让 JS 生气并向我抛出错误和异常。

Aaaaand...我已经没有想法了。有没有人有什么建议 ?

Zia*_*hal 6

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

无论如何,可以使用重置方法,并且非常适合我。

它看起来像:

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

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

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

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

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

  • 使用多个嵌套导航器时,除非设置代表指定导航器的正确对象树,否则重置将无法正常工作。如果没有,重置将仅作用于最父导航器,将索引重置为 0,尝试找到该路线并失败。因此,它将回退到初始路线或导航树中的第一条路线,具体取决于导航器的道具。我尝试通过嵌套导航器以各种方式使用重置,但不适用于此用例。 (3认同)

cri*_*ian 5

似乎 React Navigation 的文档试图用本指南涵盖这个用例:

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

那里的示例非常棘手,已经介绍了状态管理库、reducer、React 钩子以及其他任何没有真正帮助的东西。但是,该指南的摘要是: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>;
    }
}
Run Code Online (Sandbox Code Playgroud)

在本例中,调用(this.) props.setIsLoggedIn(true)渲染Home路由,或者调用带false参数返回LoginScreens路由。

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


cri*_*ian 0

最初我发布了这个解决方案: /sf/answers/4221492971/

然而,最终,由于我在条件渲染方面遇到了一些严重的卡顿问题,我最终没有使用它:
Navigator/Screen安装时,会发生很多事情,多个屏幕可能会被实例化(特别是如果您使用没有延迟安装的选项卡式导航器) ),嵌套的<Navigator />s 可能会挂载,react-navigation必须重新评估它的状态,等等。
应用程序别无选择,只能坚持直到整个路线树安装完毕后再进行渲染,这可能会导致安装之间出现空白闪烁。在低端设备上,黑屏持续的时间可能会超出用户所能容忍的时间。

我发现的更好的替代解决方案涉及对NavigationContainer.resetRoot方法的命令式调用。通过将 a 附加refNavigationContainer,调用resetRoot将始终作用于根导航状态。
resetRoot还允许指定新的导航状态,这对于更改当前活动的路线很有用。
实现如下:

libs/root-navigation.js

import React from "react";

// This is the ref to attach to the NavigationContainer instance
export const ref = React.createRef();


/**
 * Resets the root navigation state, and changes the active route to the one specified
 * @param {string} name The name of the route to navigate to after the reset
 * @param {object|undefined} params Additional navigation params to pass to the route
 */
export function navigate(name, params) {
    try {
        ref.current.resetRoot({ index: 0, routes: [{ name, params }] });
    } catch (e) {
        console.error("Failed to reset the root navigation state. Make sure you have correctly attached the ref to the <NavigationContainer /> component.\nOriginal error:", e);
    }
}
Run Code Online (Sandbox Code Playgroud)

App.js(或者无论您在何处渲染<NavigationContainer />组件:

import { NavigationContainer } from "@react-navigation/native";
import * as RootNavigation from "./libs/root-navigation";
import { createStackNavigator } from "@react-navigation/stack";
import LoginScreen from "./screens/Login";
import RegisterScreen from "./screens/Register";
import DashboardScreen from "./screens/Dashboard";
import AccountScreen from "./screens/Account";

const RootStack = createStackNavigator();
const AuthenticationStack = createStackNavigator();
const HomeStack = createStackNavigator();

function AuthenticationScreens() {
    return <AuthenticationStack.Navigator initialRouteName="Login">
        <AuthenticationStack.Screen name="Login" component={LoginScreen} />
        <AuthenticationStack.Screen name="Register" component={RegisterScreen} />
    </AuthenticationStack.Navigator>;
}

function HomeScreens() {
    return <HomeStack.Navigator initialRouteName="Dashboard">
        <HomeStack.Screen name="Dashboard" component={DashboardScreen} />
        <HomeStack.Screen name="Account" component={AccountScreen} />
    </HomeStack.Navigator>;
}

export default function MyApp() {
    // ... your awesome code :)
    return <NavigationContainer ref={RootNavigation.ref}>
        <RootStack.Navigator initialRouteName="Authentication">
            <RootStack.Screen name="Authentication" component={AuthenticationScreens} />
            <RootStack.Screen name="Home" component={HomeScreens} />
        </RootStack.Navigator>
    </NavigationContainer>;
}
Run Code Online (Sandbox Code Playgroud)

然后,在应用程序的其他位置,您始终可以navigate()root-navigation.js文件导入该函数,并使用它来重置根堆栈:

import { Pressable, Text, View } from "react-native";
import * as RootNavigation from "./libs/root-navigation";
import * as ServerAPI from "./server-api";

function LoginScreen() {
    const email = "hello@world.com";
    const password = "P@$sw0rD!";

    const onLoginPress = () => {
        ServerAPI.login(username, password).then(({ success, user })=>{
            if (success === true) {
                // Here we reset the root navigation state, and navigate to the "Home" screen
                RootNavigation.navigate("Home", { user });
            } else {
                alert("Wrong email or password...");
            }
        });
    }

    return <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
        <Pressable onPress={onLoginPress}>
            <Text>Login now!</Text>
        </Pressable>
    </View>;
}
Run Code Online (Sandbox Code Playgroud)

我更喜欢这个解决方案而不是我最初的解决方案。它也适用于react-navigation@6.x.