使用 react navigation 库的时候,其中的导航器都有自己独立的库,用到的时候要自行安装。
使用 NavigationContainer 包裹项目
NavigationContainer 为一个组件,通常我们会用它来包裹我们的根组件。
NavigationContainer 可以设置一个 ref 属性,这个 ref 可以使用 useNavigationContainerRef() 方法创建也可以是普通的 React.createRef()。ref 对象中包含所有的导航操作,比如 navigate, goBack等。
堆栈导航器 Stack Navigator
npm install @react-navigation/native-stack
在浏览器中会记录路由的历史,然后可以后退前进,这在 app 上是不提供的,所以为了实现这种效果,提供了 createNativeStackNavigator 。
使用 createNativeStackNavigator 创建堆栈导航器的时候,会返回一个对象,这个对象包含2个属性:
navigator: 组件,应该包含screen组件。screen: 组件,有几个screen组件表示导航器中有几个路由。
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home" screenOptions={{animation: 'slide_from_left'}}>
<Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Overview' }}/>
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
补充属性:
Navigator上的initialRouteName属性可以设置初始路由。Screen上的必填属性为 name 和component,指定路由名称和显示的组件。Screen上的options属性可以覆盖路由的默认行为和样式。Navigator上的screenOptions可以覆盖其下所有Screen默认行为和样式。
路由转跳
每一个 screen 组件都会接收一个 navigation 参数,navigation 提供了四种转跳页面的方式:
navigate('RouteName'): 如果转跳的页面在导航历史中,则会直接转跳到以前的记录。如果转跳的是一个新页面,则会在导航历史中添加一条记录。push('RouteName'): 不管导航历史,始终转跳一个新页面,在导航历史中添加一条记录。goBack(): 回到上一条导航历史,安卓上的硬件返回键也符合这一条。popToTop(): 回到第一条导航记录。
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
}
传递参数
每一个 screen 组件都会接收一个 route 参数,route 的 params 参数可以接收路由参数。
function DetailsScreen({ route, navigation }) {
const { itemId, otherParam } = route.params;
return ( ... );
}
我们向路由中设置参数有2种方式:
navigate和push接受第二个可选参数,以便将参数传递到导航到的路由
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
- 在
screen组件上定义
<Stack.Screen
name="Details"
component={DetailsScreen}
initialParams={{ itemId: 42 }}
/>
我们还可以通过 navigation.setParams({ ... }); 改变参数
设置导航头部
- 设置头部的
title
<Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Home' }}/>
但是,当我们希望头部标题根据参数显示的时候,我们可以在 options 中设置一个方法,这个方法的参数是包含 navigation 和 route 对象。
<Stack.Screen name="Home" component={HomeScreen}
options={({ route }) => ({ title: route.params.name })/>
我们也可以使用 navigation.setOptions({ ... }) 改变 options 中的属性。
- 使用自定义组件定义
title
function LogoTitle() {
return (
<Image style={{ width: 50, height: 50 }} source={require('@expo/snack-static/react-native-logo.png')} />
);
}
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen}
options={{ headerTitle: (props) => <LogoTitle {...props} /> }} />
</Stack.Navigator>
);
}
使用图片代替头部标题,注意这里使用的是 headerTitle 属性而不是 title 属性。当属性值是一个方法的时候,提供一个参数对象,其中包含 navigation 和 route 属性。
-
设置头部的样式:
headerStyle/headerTintColor/headerTitleStyle -
设置头部按钮 我们可以在
Stack.Screen的headerRight和headerLeft上直接定义按钮组件,实现在头部添加交互功能的按钮,但是这里添加的按钮,我们无法指向我们这个screen中的方法。所以我们更常用的是,在我们的screen组件中,使用navigation.setOptions({ ... })去添加或修改headerRight和headerLeft。
function HomeScreen({ navigation }) {
const [count, setCount] = React.useState(0);
React.useEffect(() => {badge
navigation.setOptions({
headerRight: () => (
<Button onPress={() => setCount((c) => c + 1)} title="Update count" />
),
});
}, [navigation]);
return <Text>Count: {count}</Text>;
}
我们有默认的后退按钮,当堆栈历史中存在可回退的页面时显示,我们可以使用 headerLeft 覆盖它,但是如果我们只是想改一下回退按钮的样式,我们可以使用 headerBackTitle, headerBackTitleStyle, 和 headerBackImageSource。
- 其他
options可以参考 这里。
标签导航器 Tab Navigator
npm install @react-navigation/bottom-tabs
使用createBottomTabNavigator, createMaterialBottomTabNavigator,createMaterialTopTabNavigator 创建标签导航器的时候,会返回一个对象,这个对象包含2个属性:navigator 和 screen。
const Tab = createBottomTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
我们可以看到它的用法基本和堆栈导航器一致,有一些 options 属性可能不太相同,比如修改标签 icon 的几个属性:tabBarIcon, tabBarActiveTintColor, tabBarInactiveTintColor。我们还可以通过 tabBarBadge 属性给标签添加角标。 其他更多的属性可以参考这里。
注意: 如果我们希望在一些页面上显示底部标签栏,一些页面上不显示,最好的办法是使用 Stack Navigator 嵌套 Tab Navigator。
抽屉导航器 Drawer Navigator
npm install @react-navigation/drawer
使用 createDrawerNavigator 创建抽屉导航器的时候,会从左侧弹出一个导航菜单,它返回一个对象,这个对象包含2个属性:navigator 和 screen。
const Drawer = createDrawerNavigator();
export default function App() {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Notifications" component={NotificationsScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
导航器嵌套
Stack Navigator、Tab Navigator、Drawer Navigator这三种导航器可以嵌套使用,比如下面的例子中使用 Stack Navigator 嵌套 Tab Navigator。
function Settings() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings</Text>
</View>
);
}
function Profile({ navigation }) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Profile</Text>
</View>
);
}
function Feed({ navigation }) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Feed</Text>
</View>
);
}
function Messages({ navigation }) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Messages</Text>
</View>
);
}
function Home() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={Feed} />
<Tab.Screen name="Messages" component={Messages} />
</Tab.Navigator>
);
}
export default 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>
);
}
那么在这些导航器嵌套使用的时候会遵循以下守则:
- 每个导航器保存自己的导航历史
当我们在
Feed页面navigation.navigate('Profile'),再从Profile页面navigation.navigate('Messages'),最后在Messages页面navigation.goBack()后,会回到Feed页面而不是Profile页面。 - 每个导航器都有自己的选项
options - 导航器中的每个屏幕都有自己的参数
params - 导航操作由当前导航器处理,如果无法处理就会冒泡
当我们持续调用
navigation.goBack()之后,退到了当前导航器的第一个历史记录,那么当前导航器就不能处理goBack操作了,所以它会向上冒泡,去执行父级导航器的goBack。 - 父导航器中分派动作给嵌套的子导航器,要使用
navigation.dispatch - 子导航器不会接收父导航器的事件
- 父导航器的 UI 呈现在子导航器之上
- 导航到
Tab Navigator或Drawer Navigator中的指定页面,当我们在Profile页面导航到Home中的Messages时,有两种写法:
navigation.navigate('Messages')
navigation.navigate('Home', { screen: 'Messages' })
navigation.navigate('Home', { screen: 'Messages',params: { user: 'jane' } })
导航器生命周期
可以使用 react 中本来就有的 componentDidMount、componentWillUnmount 生命周期判断页面的转跳。当堆栈导航器中有 A 、B 两个页面,当 navigator 到 A 之后,会触发 A 的 componentDidMount,当 push 到 B 的时候会触发 B 的 componentDidMount,但是此时 A 还在历史堆栈中,所以不会触发 A 的 componentWillUnmount,当我们再从 B 页面 goBack 到 A 时,会触发 B 的 componentWillUnmount,但不会触发 A 的 componentDidMount。
可以使用 useFocusEffect 或者 useIsFocused 在页面聚焦时进行操作。