react-navigation 6.x学习(2)
标题栏按钮
在头部添加按钮
与header交互最常见的方式是点击标题左边或右边的按钮。让我们在头部的右侧添加一个按钮(这是整个屏幕上最难触摸的地方,取决于手指和手机的大小,但也是放置按钮的正常位置)。
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerTitle: (props) => <LogoTitle {...props} />,
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
),
}}
/>
</Stack.Navigator>
);
}
当我们这样定义按钮时,options中的this变量不是HomeScreen实例,所以你不能调用setState或它的任何实例方法。但是,想让标题中的按钮与标题所属的屏幕交互是非常常见的。那么,我们接下来看看如何做到这一点。
header与组件的交互
想要和屏幕组件交互,需要使用navigation.setOptions
来定义按钮。在组件中使用navigation.setOptions
,我们可以访问屏幕的props,state,conext等。
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={({ navigation, route }) => ({
headerTitle: (props) => <LogoTitle {...props} />,
})}
/>
</Stack.Navigator>
);
}
function HomeScreen({ navigation }) {
const [count, setCount] = React.useState(0);
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<Button onPress={() => setCount((c) => c + 1)} title="Update count" />
),
});
}, [navigation]);
return <Text>Count: {count}</Text>;
}
自定义返回按钮
createNativeStackNavigator为后退按钮在特定平台上提供默认值。在iOS上,这包括 在按钮旁边的一个标签,当标题适合可用空间时,它会显示上一个屏幕的标题,否则它会显示“后退”。
你可以用headerBackTitle改变标签行为,用headerBackTitleStyle设置样式。
要定制后退按钮图片,可以使用headerBackImageSource
嵌套navigators
嵌套导航就是在一个Screen中再渲染另一个Screen
例如
function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Messages" component={Messages} />
</Tab.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{ headerShown: false }}
/>
<Stack.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}
在上面的例子中,在Home组件包含一个Tab 导航,而Home组件本身在App组件的stack导航中,所以一个tab导航被嵌套在stack导航中。
Stack.Navigator
Home
(Tab.Navigator
)Feed
(Screen
)Messages
(Screen
)
Profile
(Screen
)Settings
(Screen
)
嵌套导航器的工作原理很像嵌套常规组件。为了实现您想要的行为,通常需要嵌套多个导航器。
嵌套导航需要注意
1.每个导航器都保留自己的导航历史
例如,当您在嵌套堆栈导航器的屏幕内按下后退按钮时,它将返回嵌套堆栈中的前一个屏幕,即使有另一个导航器作为父导航器。
2.每个导航都有自己的options
例如,在子导航器嵌套的屏幕中指定标题 options,不会影响父导航器中显示的标题。
3.导航器(navigator)中的每个屏幕(Screen)都有自己的参数
例如,在嵌套导航器中传递给屏幕的任何参数都在该屏幕的路由支柱中,不能从父或子导航器的屏幕访问。
如果需要从子屏幕访问父屏幕的参数,可以使用React Context向子屏幕公开参数。
4.导航操作由当前导航器处理,如果不能处理,就会弹出
5.导航器特定的方法在嵌套的导航器中可用
6.嵌套导航器不接收父级事件
7.父导航器的UI呈现在子导航器之上
在嵌套导航器中导航到屏幕
例子
function Root() {
return (
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Profile" component={Profile} />
<Stack.Screen name="Settings" component={Settings} />
</Drawer.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Root"
component={Root}
options={{ headerShown: false }}
/>
<Stack.Screen name="Feed" component={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}
从Feed组件跳转到Root组件
navigation.navigate('Root');
它跳转了,并显示了Root组件内的初始屏幕,即Home。但有时您可能想要控制导航时应该显示的屏幕。为了实现它,你可以在参数中传递屏幕的名称:
navigation.navigate('Root', { screen: 'Profile' });
现在,跳转到的是Profile组件而不是Home组件。
在嵌套导航器中向屏幕传递参数
可以使用params属性传递参数:
navigation.navigate('Root', {
screen: 'Profile',
params: { user: 'jane' },
});
深层嵌套:
navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
呈现导航器中定义的初始路由
如果你需要渲染导航器中指定的初始路由,你可以通过设置initial: false来禁用
navigation.navigate('Root', {
screen: 'Settings',
initial: false,
});
嵌套多个navigators
有时嵌套多个导航器(如堆栈、抽屉或标签)是很有用的。
当嵌套多个堆栈、抽屉或底部选项卡导航器时,子导航器和父导航器的标题都会显示出来。然而,通常更可取的做法是在子导航器中显示标题,而在父导航器的屏幕中隐藏标题。
为了实现这一点,您可以使用headerShown: false选项在包含导航器的屏幕中隐藏标题。
function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Profile" component={Profile} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{ headerShown: false }}
/>
<Stack.Screen name="EditPost" component={EditPost} />
</Stack.Navigator>
</NavigationContainer>
);
}
如果你不想在任何导航器中出现头信息,你可以在所有导航器中指定headerShown: false:
function Home() {
return (
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen name="Profile" component={Profile} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="EditPost" component={EditPost} />
</Stack.Navigator>
</NavigationContainer>
);
}
我们建议将嵌套导航器减少到最少。尽量用最少的嵌套来实现您想要的行为。嵌套有很多缺点:
它导致深度嵌套的视图层次结构,这可能会导致低端设备的内存和性能问题
嵌套相同类型的导航器(例如标签嵌在标签中,抽屉嵌在抽屉中等)可能会导致用户体验混乱
由于过度嵌套,在导航到嵌套屏幕、配置深度链接等时,代码变得难以遵循。
将嵌套导航器看作是实现您想要的UI的一种方法,而不是组织代码的一种方法。如果您想为组织创建单独的一组屏幕,而不是使用单独的导航器,您可以使用group组件。
<Stack.Navigator>
{isLoggedIn ? (
// Screens for logged in users
<Stack.Group>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Profile" component={Profile} />
</Stack.Group>
) : (
// Auth screens
<Stack.Group screenOptions={{ headerShown: false }}>
<Stack.Screen name="SignIn" component={SignIn} />
<Stack.Screen name="SignUp" component={SignUp} />
</Stack.Group>
)}
{/* Common modal screens */}
<Stack.Group screenOptions={{ presentation: 'modal' }}>
<Stack.Screen name="Help" component={Help} />
<Stack.Screen name="Invite" component={Invite} />
</Stack.Group>
</Stack.Navigator>
导航的生命周期
如果你是在一个web后台使用反应式导航,你可以假设当用户从路由a导航到路由B时,a会卸载(它的组件willunmount被调用),当用户返回时,a会再次挂载。虽然这些React生命周期方法仍然有效,并用于反应式导航,但它们的用法不同于web。这是由更复杂的移动导航需求驱动的。
与 React 相关联的生命周期
假设在 stack navigator 中有,页面A,页面B。
- 进入页面A,调用
A.componentDidMount
- 进入页面B,调用
B.componentDidMount
- 离开页面B,调用
B.componentWillUnmount
- 离开页面A,调用
A.componentWillUnmount
实例场景
考虑一个带有屏幕a和b的堆栈导航器。在导航到a之后,它的componentDidMount被调用。当push B时,它的componentDidMount也被调用,但A仍然挂载在堆栈上,因此它的componentWillUnmount不被调用。
当从B返回到A时,B的componentWillUnmount被调用,但A的componentDidMount不被调用,因为A一直处于挂载状态。
function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="First">
{() => (
<SettingsStack.Navigator>
<SettingsStack.Screen
name="Settings"
component={SettingsScreen}
/>
<SettingsStack.Screen name="Profile" component={ProfileScreen} />
</SettingsStack.Navigator>
)}
</Tab.Screen>
<Tab.Screen name="Second">
{() => (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={HomeScreen} />
<HomeStack.Screen name="Details" component={DetailsScreen} />
</HomeStack.Navigator>
)}
</Tab.Screen>
</Tab.Navigator>
</NavigationContainer>
);
}
我们从Home页面导航到Details页面,然后我们使用Tab切换到Settings,在2跳转到Profile。完成这一系列操作后,4个Sceen都被挂载。如果你使用标签栏切换回Home,你会注意到你会出现DetailsScreen,HomeStack的导航状态被保留了!
导航生命周期事件
现在,让我们来回答我们在开始时提出的问题:“我们如何发现用户是离开(模糊)它还是回到(聚焦)它?”
React Navigation向订阅事件的屏幕组件发出事件。我们可以通过监听focus和blur事件来知道屏幕分别何时聚焦或失焦。
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// Screen was focused
// Do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
我们可以使用useFocusEffect钩子来执行副作用,不用手动添加事件监听器。它类似于React的useEffect钩子,但它与导航生命周期相关联。
import { useFocusEffect } from '@react-navigation/native';
function Profile() {
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, [])
);
return <ProfileContent />;
}
如果你想根据屏幕是否聚焦来呈现不同的东西,你可以使用useIsFocused钩子,它返回一个布尔值来指示屏幕是否聚焦。
总结
1.虽然React的生命周期方法仍然有效,但React Navigation添加了更多的事件,您可以通过navigation prop订阅这些事件。
2.你也可以使用useFocusEffect或useIsFocused钩子。
术语表
Navigator
Navigator是React组件,它决定如何呈现已定义的屏幕。它包含Screen元素作为子元素,用于定义屏幕的配置。
NavigationContainer是一个管理导航树并包含导航状态的组件。该组件必须封装所有导航器结构。通常,我们会在应用的根目录渲染这个组件,它通常是从app .js导出的组件。
function App() {
return (
<NavigationContainer>
<Stack.Navigator> // <---- This is a Navigator
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
Router
路由器是一组函数的集合,它们决定如何处理导航器中的操作和状态变化(类似于Redux应用程序中的reducer)。通常情况下,您永远不需要与路由器直接交互,除非您正在编写一个自定义导航器。
Screen component
屏幕组件是我们在路由配置中使用的组件。
const Stack = createNativeStackNavigator();
const StackNavigator = (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen} // <----
/>
<Stack.Screen
name="Details"
component={DetailsScreen} // <----
/>
</Stack.Navigator>
);
组件名称中的Screen后缀完全是可选的,但这是一种常用的约定;我们可以叫它迈克尔,这也是一样的。
Navigation Prop
这个属性将被传递给所有Screen,它可以用于以下用途:
1.Dispatch将向路由器发送一个动作
2.navigate, goBack等可以方便地调度动作
3.导航器也可以接受导航道具,如果有的话,它们应该从父导航器中获得。
Route Prop
这个属性会传送到所有屏幕。包含当前路由的信息,如params
, key
and name
。
Navigation State
{
key: 'StackRouterRoot',
index: 1,
routes: [
{ key: 'A', name: 'Home' },
{ key: 'B', name: 'Profile' },
]
}
对于这种导航状态,有两个路由(可能是选项卡,也可能是堆栈中的卡片)。索引表示活动路由,为B。
Route
每个路由都是一个对象,它包含一个用来标识它的键和一个用来指定路由类型的“名称”。它也可以包含任意参数:
{
key: 'B',
name: 'Profile',
params: { id: '123' }
}
Header
也就是导航头,导航栏,应用栏,还有很多其他东西。这是屏幕顶部的矩形,包含后退按钮和屏幕标题。整个矩形通常被称为React Navigation中的标题。