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
挂钩,具有以下内容:
- 对 React Native 的
BackHandler
进行回调,从处理程序返回 true 有效(true 表示后退操作已处理,不会调用进一步的后退处理程序),但它也会阻止内部的任何后退导航Home
中的屏幕(例如,我无法从 Dashboard 导航回 MyProfile)。
- 使用
navigation.reset({ index: 1, routes: [{ name: "Home" }] })
。如果没有 index: 1
,导航只会返回到 ROOT 的 initialRoute(在本例中,LoginScreens
)。对于 index: 1
,抛出 Maximum update depth exceeded
错误。
- 而不是直接导航到
Home
,我尝试使用 navigation.reset()
(注意:没有参数,清除整个导航历史记录),然后导航到 Home
屏幕.这没有达到预期的效果,因为在导航到 Home
. 之前,当前路线(ROOT 的 initialRoute,在本例中为:LoginScreens
)仍然被推送到导航历史记录中
- 以不同的方式组合导航和重置调用,我只设法让 JS 生气并向我抛出错误和异常。
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 来处理对一个项目的每次按下:
(注:无视header
和footer
属性,只是我抽屉的调整。)
...
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);
并在需要时调用它。
我在一个项目中使用 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
挂钩,具有以下内容:
- 对 React Native 的
BackHandler
进行回调,从处理程序返回 true 有效(true 表示后退操作已处理,不会调用进一步的后退处理程序),但它也会阻止内部的任何后退导航Home
中的屏幕(例如,我无法从 Dashboard 导航回 MyProfile)。 - 使用
navigation.reset({ index: 1, routes: [{ name: "Home" }] })
。如果没有index: 1
,导航只会返回到 ROOT 的 initialRoute(在本例中,LoginScreens
)。对于index: 1
,抛出Maximum update depth exceeded
错误。 - 而不是直接导航到
Home
,我尝试使用navigation.reset()
(注意:没有参数,清除整个导航历史记录),然后导航到Home
屏幕.这没有达到预期的效果,因为在导航到Home
. 之前,当前路线(ROOT 的 initialRoute,在本例中为: - 以不同的方式组合导航和重置调用,我只设法让 JS 生气并向我抛出错误和异常。
LoginScreens
)仍然被推送到导航历史记录中
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 来处理对一个项目的每次按下:
(注:无视header
和footer
属性,只是我抽屉的调整。)
...
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);
并在需要时调用它。