本章我们将主要学习 React Native 应用中的路由嵌套和页面跳转。
绘制 Center 页面
之前我们已经绘制了 Main 和 Community 两个页面,现在我们来补全 Center 页面。
center.js
import React from 'react';
import {
View,
Text,
TouchableOpacity
} from 'react-native';
import {
Avatar,
Divider
} from '@react-native-elements/base';
// 引入图标
import AntDesign from 'react-native-vector-icons/AntDesign';
import styles from './styles';
const Center = ({ navigation }) => {
return (
<View style={styles.container}>
<View style={styles.settingBox}>
<TouchableOpacity onPress={() => navigation.navigate('Setting')}>
<AntDesign
name="setting"
size={28}
/>
</TouchableOpacity>
</View>
<View style={styles.headerBox}>
<Avatar
size={80}
rounded
containerStyle={styles.headerIcon}
source={{uri: 'https://somoskudasai.com/wp-content/uploads/2020/09/51qswBACKwL._AC_.jpg'}}
/>
<View style={styles.headerMess}>
<View style={styles.headerTopList}>
<View style={styles.headerTop}>
<Text style={styles.headerTopTitle}>1.2K</Text>
<Text style={styles.headerTopSubtitle}>获赞</Text>
</View>
<View style={styles.headerTop}>
<Text style={styles.headerTopTitle}>1678</Text>
<Text style={styles.headerTopSubtitle}>关注</Text>
</View>
<View style={styles.headerTop}>
<Text style={styles.headerTopTitle}>720</Text>
<Text style={styles.headerTopSubtitle}>粉丝</Text>
</View>
</View>
<TouchableOpacity style={styles.editMessBtn}>
<Text style={{textAlign:'center'}}>编辑个人信息</Text>
</TouchableOpacity>
</View>
<View style={styles.userMessage}>
<Text style={styles.userName}>Mirai3901</Text>
<Text style={styles.userIntro}>初音ミク是2007年8月31日由 Crypton Future Media 以雅马哈的 Vocaloid 系列语音合成程序为基础开发的音源库,音源数据资料采样于日本声优藤田咲。</Text>
</View>
</View>
<View style={styles.orderBox}>
<Text style={styles.orderTitle}>我的订单</Text>
<Divider />
<View style={styles.orderList}>
<View style={styles.orderItem}>
<AntDesign name="wallet" size={18} color="#000"/>
<Text style={styles.orderText}>待付款</Text>
</View>
<View style={styles.orderItem}>
<AntDesign name="car" size={18} color="#000"/>
<Text style={styles.orderText}>待发货</Text>
</View>
<View style={styles.orderItem}>
<AntDesign name="save" size={18} color="#000"/>
<Text style={styles.orderText}>待收货</Text>
</View>
<View style={styles.orderItem}>
<AntDesign name="pay-circle-o1" size={18} color="#000"/>
<Text style={styles.orderText}>售后</Text>
</View>
</View>
</View>
</View>
);
};
export default Center;
styles.js
import { StatusBar, StyleSheet, Dimensions } from 'react-native';
// 获取窗口的宽高,且可以在尺寸变化时自动更新
const { width } = Dimensions.get('window');
/**
* 注意:
* 1. 表示的是与设备像素密度无关的逻辑像素点
*/
const styles = StyleSheet.create({
container:{
width: '100%',
height: '100%',
backgroundColor: '#F5F5F5'
},
settingBox:{
width:'100%',
paddingVertical: 10,
paddingRight: 20,
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
backgroundColor: '#FFF'
},
headerBox:{
width: '100%',
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
backgroundColor: '#FFF',
padding: 20,
borderRadius: 4
},
headerIcon:{
marginRight: 40
},
headerMess:{
flex: 1
},
headerTopList:{
display: 'flex',
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between'
},
headerTop:{
},
headerTopTitle:{
fontSize: 16,
textAlign: 'center'
},
headerTopSubtitle:{
fontSize: 12,
textAlign: 'center'
},
editMessBtn:{
width: '100%',
textAlign: 'center',
paddingVertical: 4,
margin: 0,
borderWidth: 1,
borderColor: '#333'
},
userMessage:{
width: '100%',
paddingTop: 20
},
userName:{
fontSize: 18,
marginBottom: 2
},
userIntro:{
fontSize: 12,
color: '#aaa'
},
orderBox:{
marginTop: 10,
backgroundColor: '#FFF',
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 4
},
orderTitle:{
marginBottom: 10,
fontSize: 16,
fontWeight: 'bold'
},
orderList:{
width: '100%',
display: 'flex',
marginTop: 14,
flexDirection: 'row',
justifyContent: 'space-between'
},
orderItem:{
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
orderText:{
fontSize: 12,
marginTop: 6,
fontWeight: 'bold'
}
});
export default styles;
在 center.js 中,我们新增了一个 navigation 参数,并在 TouchableOpacity 组件中调用了 navigation.navigate 方法来实现页面跳转,这是 React Navigation 的标准跳转方式,我们将在后续详细讲解。
调整路由
接下来我们调整应用的入口页面,将路由相关的功能剥离出来,统一管理。首先修改 app/index.js 文件。
app/index.js
import React from 'react';
import { StatusBar } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
// 获取路由
import Router from './pages/router';
const App = () =>{
return (
<NavigationContainer>
<StatusBar hidden={true} translucent={true}/>
<Router/>
</NavigationContainer>
);
};
export default App;
然后在 pages 目录下创建 router.js 文件,负责管理所有页面的跳转逻辑。这里我们新增了三个页面:商品详情页 FurnDetail、设置页 Setting 和登录页 Login。目前这些页面仅用于测试跳转,没有复杂逻辑,只包含简单的文本展示,你可以根据代码中的路径自行创建。
pages/router.js
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
// 页面列表
import Main from './main/main';
import Community from './community/community';
import Center from './center/center';
// 新增三个页面
import FurnDetail from './furnDetail/furnDetail';
import Setting from './center/setting/setting';
import Login from './login/login';
// 创建堆栈导航,可以存放打开过的页面
const Tab = createBottomTabNavigator();
const Stack = createNativeStackNavigator();
// 导航全局配置,包裹下的所有导航均会使用该样式。
const screenOption = {
headerShown:false,
tabBarInactiveTintColor: '#999999', // 非选中文字和图标的颜色
tabBarActiveTintColor: '#39c5bb', // 选中文字和图标的颜色
tabBarHideOnKeyboard: true, // 打开键盘时,选项卡是否隐藏
// tabBar的样式,根据当前触发的tab
tabBarStyle: {
borderTopColor: 'rgba(0, 0, 0, .2)' // 导航栏顶部边框颜色
}
};
// 首页
const Tabs = () => (
<Tab.Navigator
screenOptions={screenOption}>
<Tab.Screen
name="Main"
component={Main}/>
<Tab.Screen
name="Community"
component={Community}/>
<Tab.Screen
name="Center"
component={Center}/>
</Tab.Navigator>
);
const Router = () =>{
return (
<Stack.Navigator
initialRouteName="Tabs"
screenOptions={screenOption}>
{/* 首页tab页面 */}
<Stack.Screen
name="Tabs"
component={Tabs}/>
{/* 页面 */}
<Stack.Screen
name="Login"
component={Login}/>
<Stack.Screen
name="FurnDetail"
component={FurnDetail}/>
<Stack.Screen
name="Setting"
component={Setting}
options={{
title: '设置',
headerShown: true
}}/>
</Stack.Navigator>
);
};
export default Router;
这里可以看到,我们在 Tab 组件外包裹了一个 Stack 组件,并将 Tabs 设为默认路由,确保用户进入应用后首先看到底部导航栏页面。
React Navigation 层级设置讲解
根路由要设置为 Stack.Navigation
我们的根节点由 Stack 路由包裹,然后将 Tab 嵌套在子路由中。虽然 Setting 页面逻辑上归属于 Center,FurnDetail 归属于 Main,但我们并没有将它们直接放在 Tab 路由下。
如果我们将路由设置为以 Tab 为根节点,然后嵌套使用 Stack,那么所有跳转的页面都会嵌套在 Tab 下,这会导致两个问题:
- 跳转到子路由时,底部
Tab栏仍然会显示。 - 如果有公共页面(如
Login页面),我们无法直接在根节点添加。
因此,根路由必须使用 Stack 来包裹。
页面间跳转
React Navigation 提供了两种跳转方法:
navigation.navigate(路由名, params)
<TouchableOpacity onPress={() => navigation.navigate('Setting', { id: '2' } )}>
<AntDesign name="setting" size={28} />
</TouchableOpacity>
Link方法
import { Link } from '@react-navigation/native';
...
<Link to={{ screen: 'Setting', params: { id: '1' } }}>
<AntDesign name="setting" size={28} />
</Link>
在目标页面(如 setting.js)中,可以通过 route.params 获取传递的参数:
const Setting = ({ navigation, route }) => {
console.log(route.params); // 输出:{"id": "1"}
return (
<View style={styles.container}>
<Text>Setting</Text>
</View>
);
};
navigation 跳转常用方法
以下列举一些常用的 navigation 跳转方法,更多高级用法(如 dispatch)可参考官方文档。
replace
将当前路由重定向为指定路由。
navigation.replace('Setting', params);
goBack
关闭当前页面并返回上一页。如果需要返回到指定的页面,可以指定跳转页面的 key。
// 跳转时指定 key
navigation.navigate({ name: 'SCREEN', key: 'SCREEN_KEY_A' });
// 返回到指定 key 的页面
navigation.goBack({ key: 'routeKey' });
navigate
跳转到指定页面。
navigation.navigate('Setting', { id: '2' });
setParams
更新当前页面的 params 参数。如果参数已存在则覆盖,不存在则添加。
// 原 route.params = { id: 1 };
navigation.setParams({ id: 2, name: 'Mirai' });
// 更新后 route.params = { id: 2, name: 'Mirai' };
setOptions
更新当前路由的 options 配置。
// 在路由配置中设置
<Stack.Screen name="Login" component={Login} options={{ title: '登录' }} />
// 在页面中动态更新
navigation.setOptions({ title: '用户登录' });
isFocused
判断当前页面是否获得焦点,获得焦点返回 true,否则返回 false。