React Native 基于Expo开发(三)路由,跳转

4,110 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

什么是导航?

先简单介绍一个移动端界面的概念:

  1. 现在流行的Vue和React开发的Web端应用,都是单页面应用(single-page-application),在Web端想从A界面进入B界面,本质上是对当前页面dom的更换,路由只是做了虚假的切换
  2. 移动端的界面切换不同于Web端。在移动端一个界面就是一个独立的界面,在iOS应用中这个界面被称作 ViewController ,在安卓应用中这个界面被称作 Activity 。(后面统一称作界面)从 A 界面进入 B 界面,就需要将 B 这个 viewController/activity 放到整个导航栈的最上层,在内存中的表现就是一个压栈的操作。这个时候点击返回或者左滑,回到了 A 界面,那么 B 界面这个时候在内存中就被销毁了。

为什么移动端需要这么做:(个人理解)

  1. 因为移动端的屏幕小,苹果和安卓的设计者们希望在有限的屏幕里展示出很多好看的动画效果,所以在界面切换时,会有很好看的渐入渐出的效果(比如iphone手机的左滑返回功能,大部分android手机也支持,但是android的左滑返回和iOS的左滑返回,本质上完全是两码事,这个具体差别在哪,就不细谈了,一句两句也说不清楚,自行google)
  2. 第二个点是手机的内存都不大,这样会很好的管理内存,当某个界面不需要时,就出栈了,在内存中销毁了

在React Native中如何创建界面?如何跳转?

导航

上面之所以介绍什么是导航,就是想引申出一个"导航"的概念。在移动端,你的这个界面必须是导航界面,才有进入下一个界面和返回上一个界面的功能。

流程图.jpg 上图是我画的一个基本的跳转的流程图。我是做iOS出身的,所以原谅我用iOS的概念来说明,其实安卓在这块和iOS是差不多的。

一个界面想要拥有导航的功能,就需要成为导航界面的子界面。注意这里的导航界面他本身不是一个界面,他需要至少一个界面成为他的子界面,才能显示到我们面前。 一般第一个子界面我们称作根界面,然后我们需要跳转到详情界面,这个时候,我们就会看到详情界面的有了默认的一个返回按钮,说明在详情页的下面还压着一个首页,我们只需要返回就可以回到首页。

React Navigation

这个三方目前是React Native最好用也是expo官方的。链接在这里

安装 这些东西具体代表什么,自行查阅官网,如有不理解的,可以一起讨论,反正都安装下来就对了

npm install @react-navigation/native
expo install react-native-screens react-native-safe-area-context  
npm install @react-navigation/native-stack

基本使用

这里怎么使用,我就不细说了,如果能理解我上面说的关于导航的概念,大家自己去官网的使用就好了。我主要说一下我们常用的一些方法,和路由的配置。

  1. 我的项目是一个tabbar类型的项目,就是底部有菜单的那种。这个就需要用到这个三方里面另外一个东西,自行安装一下:npm install @react-navigation/bottom-tabs,就是这种类型的项目:

xxx.jpg

  1. 我的项目需要判断用户是否登录或者登录是否过期,来决定第一次展示的是登录页还是首页。这个具体怎么判断,在哪判断,看我上一篇文章,React Native 基于Expo开发(二)启动页

贴出我的代码:

MainStackScreen.js

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';

import LaunchScreen from '../screen/login/LaunchScreen'; // 这是我的登录页的第一个界面
import BottomTab from './BottomTab'; // 这是tabbar界面
import { navigationRef } from './RootNavigation'; // 这个是封装的一个工具,用来进行在js代码中的跳转的
import stacks from './index'; // 这个文件里面放了项目中所有用到的界面

const MainStack = createNativeStackNavigator();

const MainStackScreen = (props) => {
  return (
    <NavigationContainer ref={navigationRef}>
      <MainStack.Navigator initialRouteName={props.root}>
        {/* tab */}
        <MainStack.Screen name="Tab" component={BottomTab} options={{ headerShown: false }} />
        {/* LaunchScreen */}
        <MainStack.Screen name="LaunchScreen" component={LaunchScreen} options={{ headerShown: false }} />
        {/* all screens */}
        {stacks.map((item, index) => (
          <MainStack.Screen key={index.toString()} name={item.name} component={item.component} options={item.options} />
        ))}
      </MainStack.Navigator>
    </NavigationContainer>
  );
};

export default MainStackScreen;

App.js

// 这个root是根据业务来决定的,具体的看我上一篇文章
return (
    <RootSiblingParent>
      <MainStackScreen root={root} />
    </RootSiblingParent>
  );

BottomTab.js(项目中用到了国际化,所以I18n里面的都是一些文本信息而已)

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import React from 'react';
import { Image } from 'react-native';

import tabImage from '../img/tab/tabImage';
import I18n from '../language/index';
import HomeScreen from '../screen/home/HomeScreen';
import MinePage from '../screen/mine/Root';
import VehiclePage from '../screen/vehicle/Root';

const BottomTabs = createBottomTabNavigator();

export default function BottomTabScreen() {
  return (
    <BottomTabs.Navigator
      initialRouteName="Vehicle"
      screenOptions={({ route }) => ({
        tabBarActiveTintColor: '#3C476C',
        tabBarIcon: ({ focused }) => {
          return <Image source={focused ? tabImage[`${route.name}_active`] : tabImage[route.name]} style={{ width: 24, height: 24 }} />;
        },
      })}
    >
      <BottomTabs.Screen
        name="Vehicle"
        component={VehiclePage}
        options={{
          headerShown: false,
          title: I18n.t('menu.MENU_HOME_MY_VEHICLE'),
          tabBarLabel: I18n.t('menu.MENU_HOME_MY_VEHICLE'),
        }}
      />
      <BottomTabs.Screen
        name="Home"
        component={HomeScreen}
        options={{
          title: I18n.t('menu.MENU_HOME_BATTERY_SWAP'),
          tabBarLabel: I18n.t('menu.MENU_HOME_BATTERY_SWAP'),
        }}
      />
      <BottomTabs.Screen
        name="MyAccount"
        component={MinePage}
        options={{
          headerShown: false,
          title: I18n.t('menu.MENU_HOME_MY_ACCOUNT'),
          // tabBarLabel: I18n.t('menu.MENU_HOME_MY_ACCOUNT'),
        }}
      />
    </BottomTabs.Navigator>
  );
}

RootNavigation.js

import { createNavigationContainerRef, StackActions } from '@react-navigation/native';

export const navigationRef = createNavigationContainerRef();

export function navigate(name, params) {
  if (navigationRef.isReady()) {
    navigationRef.navigate(name, params);
  }
}

export function replace(name, params) {
  if (navigationRef.isReady()) {
    navigationRef.dispatch(StackActions.replace(name, params));
  }
}

export function resetRoot(name, params) {
  if (navigationRef.isReady()) {
    navigationRef.resetRoot({ index: 0, routes: [{ name }] });
  }
}

inde.js的代码我就不全贴了,太多了,项目中所有的界面,贴一部分:

import LoginScreen from '../screen/login/LoginScreen';
import StoreDetailScreen from '../screen/home/StoreDetailScreen';
import UserDetails from '../screen/mine/userInformation/userDetails';

export default [
  // 登录界面
  {
    name: 'LoginScreen',
    component: LoginScreen,
    options: { ...headerOptions, headerTransparent: false },
  },
  // 门店详情
  {
    name: 'StoreDetailScreen',
    component: StoreDetailScreen,
    options: headerOptions,
  },
  // 用户详情
  {
    name: 'UserDetails',
    component: UserDetails,
    options: headerOptions,
  },
 ]

以上就是我所有的导航路由配置

// 跳转界面
props.navigation.navigate('Home');
// 也可以导入RootNavigation.js
rootNavigation.navigate('Home');

// 如果你想A界面进入B界面,B界面进入C界面,但是C界面返回是,不要经过B界面,使用replace方法跳转
// 在B界面跳转C时:
rootNavigation.replace('CScreen');

// 还有第三种情况:比如退出登录功能,这个时候就不是简单的从个人设置界面进入到登录界面了,而是需要将整个导航栈的根界面改为登录界面
rootNavigation.resetRoot('LoginScreen');

关于React Navigation,我也只是略知皮毛,这个三方很强大,还有很多其他的功能,比如导航栏上的按钮配置,自定义导航栏,Link功能等等,需要再去了解。开发过程中,如果有什么不明白的,可以积极评论,大家一起讨论,知无不言。

这里有一个问题想问一下偶然路过的大佬: 在iOS和安卓里面,除了几个根界面,我们是提前创建的,其他界面都是用到的时候再去创建,然后不用就销毁了。但是这个React Navigation,如果你看到我上面贴的代码就知道,我是在项目刚启动就全部注册了所有的界面,请问这个时候,所有的界面都在内存中被创建了吗?还是用到的时候才真正在内存中创建?如果调用了他的back方法,这个界面在内存中真正的被销毁了吗?求告知