React Native 路由嵌套与跳转实战指南

Viewed 0

本章我们将主要学习 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 页面逻辑上归属于 CenterFurnDetail 归属于 Main,但我们并没有将它们直接放在 Tab 路由下。

如果我们将路由设置为以 Tab 为根节点,然后嵌套使用 Stack,那么所有跳转的页面都会嵌套在 Tab 下,这会导致两个问题:

  1. 跳转到子路由时,底部 Tab 栏仍然会显示。
  2. 如果有公共页面(如 Login 页面),我们无法直接在根节点添加。

因此,根路由必须使用 Stack 来包裹。

页面间跳转

React Navigation 提供了两种跳转方法:

  1. navigation.navigate(路由名, params)
<TouchableOpacity onPress={() => navigation.navigate('Setting', { id: '2' } )}>
  <AntDesign name="setting" size={28} />
</TouchableOpacity>
  1. 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 跳转方法,更多高级用法(如 dispatch)可参考官方文档。

replace

将当前路由重定向为指定路由。

navigation.replace('Setting', params);

goBack

关闭当前页面并返回上一页。如果需要返回到指定的页面,可以指定跳转页面的 key

// 跳转时指定 key
navigation.navigate({ name: 'SCREEN', key: 'SCREEN_KEY_A' });
// 返回到指定 key 的页面
navigation.goBack({ key: 'routeKey' });

跳转到指定页面。

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

0 Answers