RN:react-navigation搭建项目导航框架

目录

一. 项目导航框架的结构
二. 项目导航框架的实现

react-navigation的一些基础知识和常用API,本文就不再讲解了,可以去它的官网查阅并学习。那本文着重讲解的是使用react-navigation搭建项目导航框架的结构及实现。


一. 项目导航框架的结构


我们先回顾一下iOS项目导航框架的结构:UITabBarController作为根容器,然后每个tabBar item对应一个UINavigationController,而每个UINavigationController都拥有一个导航栈。

对应到RN里:BottomTabNavigator作为根容器,然后每个tabBar item对应一个StackNavigator,而每个StackNavigator都拥有一个导航栈。

RN里如果真得使用这种项目导航框架的结构,使用习惯虽然跟我们iOS里比较像,但是它使用起来却不是我们iOS里那样。很简单一个例子,如果我们有一个详情界面,四个tabbar都有可能跳到这个详情界面,那我们就得把详情界面分别添加到每个StackNavigator的路由里,这代码明显是重复的,我们iOS里可不需要这么做,而且一旦实际开发中类似这样好多个界面都有可能有四个入口,那写起来就会炸掉。

因此不建议在RN里使用类似于iOS的那种导航框架,而是采用类似于安卓的一种导航框架:StackNavigator作为根容器,然后把一个BottomTabNavigator放进StackNavigator里,但是BottomTabNavigator的每个tabBar item需对应一个不带导航栏的界面。这类似于一口井,StackNavigator就是这口井,井盖就是StackNavigator的导航栏,而BottomTabNavigator就像一个排桶架,我们可以往排桶架上面放入多个没有盖的水桶——即界面。这样我们每push一个界面,其实都是往水井里放一个东西,盖在原来的排桶架上,也就是push进来的界面和BottomTabNavigator是同级别的,这其实很违反我们看界面效果直观上的理解,但这种方式在RN和安卓里编写起代码来比较合理。同时我们也可以发现这种结构有一个问题那就是:我们无法设置BottomTabNavigator上每个页面的导航栏,这需要额外的处理,因为我们看到的永远只能是最外层StackNavigator的导航栏,也就是说我们只能看到井盖,即便你给水桶盖了盖,别人也看不到。

以上就是项目导航框架的主体,但是实际开发中我们是肯定会为App添加启动页、引导页、广告页等,所以此时我们还需要给项目的主体导航框架外套上一层,即SwitchNavigator

而且我们又知道使用react-navigation,必须得用createAppContainer()包装一下根组件才能用,因此SwitchNavigator外面还得再套一层AppContainer

于是我们就得到了最终项目导航框架的结构,一共四层,从内到外依次是:BottomTabNavigatorStackNavigatorSwitchNavigatorAppContainer我们实际开发中搭建项目导航框架,也是按着这个,从内往外搭就行了:

  • 第一步:先搭最内层的BottomTabNavigator
  • 第二步:后搭根容器StackNavigator,并把BottomTabNavigator放进根容器StackNavigator里。
  • 第三步:再搭SwitchNavigator,并把根容器StackNavigator放进SwitchNavigator里。
  • 第四步:最后给SwitchNavigator套一层AppContainer,就可以使用了。


二. 项目导航框架的实现


  • 添加react-navigation相关的组件,并Link原生所有的依赖。
yarn add react-navigation

yarn add react-native-gesture-handler

react-native link react-native-gesture-handler
  • 搭建BottomTabNavigator

一些注意的地方:

1、BottomTabNavigator类似于我们iOS的UITabBarController,专门用来负责tabBar下面这一部分和四个模块首页的展示,它是无法配置导航栏的

2、当我们把BottomTabNavigator作为别的容器的路由时,它也就有了navigation属性,BottomTabNavigator的navigation属性不是说不能用来做跳转,肯定能,但是它的跳转效果仅仅是点击tabBar切换页面那种效果,不是我们常见到的那种push或者modal的效果,而且即便它有push或模态的效果,我们也不可能用它来做整个App中界面的跳转,因为我们每往BottomTabNavigator中添加一个路由,tabBar上就会多一个tab,这根本不是我们想要的效果。

3、而且到了后面的开发中,我们不会像下面代码中那样去配置每个页面的导航栏,而是会自定义导航栏,在每个界面中分别添加,这样代码会更低耦合和灵活。

// BottomTabNavigator.js

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Entypo from 'react-native-vector-icons/Entypo';

import {createBottomTabNavigator} from "react-navigation";

import Color from '../Const/Color';

import FavoritePage from "../page/FavoritePage";
import TrendingPage from "../page/TrendingPage";
import PopularPage from "../page/PopularPage";
import MyPage from "../page/MyPage";
import NavigationUtil from "./NavigationUtil";

const BottomTabNavigator = createBottomTabNavigator({
    // 路由配置
    PopularPage: {
        screen: PopularPage,
        navigationOptions: {
            tabBarLabel: '最热',
            tabBarIcon: ({tintColor}) => (
                
            ),
        },
    },
    TrendingPage: {
        screen: TrendingPage,
        navigationOptions: {
            tabBarLabel: '趋势',
            tabBarIcon: ({tintColor}) => (
                
            ),
        },
    },
    FavoritePage: {
        screen: FavoritePage,
        navigationOptions: {
            tabBarLabel: '收藏',
            tabBarIcon: ({tintColor}) => (
                
            ),
        },
    },
    MyPage: {
        screen: MyPage,
        navigationOptions: {
            tabBarLabel: '我的',
            tabBarIcon: ({tintColor}) => (
                
            ),
        },
    },
}, {
    tabBarOptions: {
        // 选中颜色
        activeTintColor: Color.THEME_COLOR,
        // 未选中颜色
        inactiveTintColor: Color.INACTIVE_TINT_COLOR,
    }
});
// 这个方法会走的前提是BottomTabNavigator被放在了另一个容器视图里作为路由,否则它是没有navigation的
BottomTabNavigator.navigationOptions = ({navigation}) => {
    const {routeName} = navigation.state.routes[navigation.state.index];

    switch (routeName) {
        // case 'PopularPage': return {header: (// 如果某个页面的导航栏就是个TopNavigator,也可以在这里配置没问题,
        //      // 但是因为TopNavigator可能要操作很多界面,都配置在这里让这个文件显得有点累赘,所以我们就去相应的界面里配置它了,而不在这里配置
        //      
        //  )};
        //  break;
        case 'PopularPage': return {header: null};
            break;
        case 'TrendingPage': return {headerTitle: '趋势'};
            break;
        case 'FavoritePage': return {headerTitle: '收藏'};
            break;
        case 'MyPage': return {headerTitle: '我的'};
            break;
    }
};

export default BottomTabNavigator;
  • 搭建StackNavigator

一些注意的地方:

前面提到了BottomTabNavigator是专门用来做底部的tabbar和四个模块首页界面的展示的,而且也提到了它是无法配置导航栏的和我们不可能用它的navigation做界面跳转(不是不能,是用的效果和我们预期不一样),这就引出了StackNavigator,这是我们非得用它不可的理由。

StackNavigator专门用来配置每个界面的导航栏和做界面的跳转,App中所有界面的跳转都必须用StackNavigatornavigation,但同样StackNavigator也必须得作为别人的路由存在时,它才有navigation属性,否则没有。

上面一段我们说了一句话“App中所有界面的跳转都必须用StackNavigatornavigation属性”,其实这句话说的有点绝对了,其实每个界面的navigation属性都可以用来做跳转,只不过有的情况下会出现问题,如果学的不好,我们找bug很难找,比如你现在可以打开PopularPage.js界面,看看里面PopularTabPage组件里打得注释就知道有可能出现的问题了,所以我们还是建议整个App中全部使用StackNavigatornavigation属性做跳转,反正它是App的根容器嘛,所有的界面都在它里面,它们之间是可以随便跳转的,可以省去很多麻烦,就像我们iOS里在同一个导航栈下的所有界面其实都是用self.navigationController来做跳转的,而所有界面的self.navigationController其实都是同一个,都是栈底父容器的那个navigationController。此时你也可以想一下,DeatilPage其实和BottomTabNavigator的是同级的,但DeatilPagePopularPageTrendingPageFavoritePageMyPage不是同级的啊,但它们之间还是可以跳转,所以这表明只要界面和界面之间在一个大容器里就可以跳转。

// StackNavigator.js

import {createStackNavigator} from "react-navigation";

import NavigationUtil from "./NavigationUtil";
import Color from '../Const/Color';

import BottomTabNavigator from './BottomTabNavigator';
import DynamicBottomTabNavigator from './DynamicBottomTabNavigator';
import DetailPage from "../page/DetailPage";


const StackNavigator = createStackNavigator({
    // 路由配置
    // BottomTabNavigator: BottomTabNavigator,
    DynamicBottomTabNavigator: DynamicBottomTabNavigator,

    DetailPage: {
        screen: DetailPage,
        navigationOptions: {
            headerTitle: '详情',
        }
    },
}, {
    defaultNavigationOptions: ({navigation}) => {
        // 注意:通过navigationOptions或defaultNavigationOptions的{navigation}获取到的navigation都是它内部包含的路由的navigation属性
        // 而且它内部有几个子路由,这个箭头函数就会走几次,全部获取给你获取到

        // 因此,StackNavigator的navigation属性其实应该在它所在的容器里获取,即SwitchNavigator
        return {
            headerStyle: {
                backgroundColor: Color.THEME_COLOR,
            },
            headerTitleStyle: {
                color: 'white',
            },
            headerBackTitle: '返回',
            headerBackTitleStyle: {
                color: 'white',
            },
            headerTintColor: 'white',
        }
    },

    mode: 'modal',
});

export default StackNavigator;
  • 搭建SwitchNavigator

一些注意的地方:

如果我们要做启动页、引导页、广告页这种只展示一次,就跳转到其它页面的效果,常规情况下还是会想到用navigate方法来跳转,但是goBack方法却无法想安卓的finish方法一样把启动页、引导页、广告页从栈里面清除掉,因此iOS侧滑或者安卓的虚拟返回按键还能返回到启动页、引导页、广告页,这就不对了。

因此RN提供了SwitchNavigator,它的用途就是一次只显示一个页面,跳转后清理掉栈内跳转之前的界面,类似于我们iOS里的切换window的rootViewController——即切换项目根容器的效果。

// SwitchNavigator.js

import {createSwitchNavigator} from "react-navigation";

import WelcomePage from "../page/WelcomePage";
import StackNavigator from './StackNavigator';
import Color from "../Const/Color";
import NavigationUtil from "./NavigationUtil";


const SwitchNavigator = createSwitchNavigator({
    // 路由配置
    WelcomePage: WelcomePage,// 启动页、引导页、广告页
    StackNavigator: StackNavigator,
}, {
    defaultNavigationOptions: ({navigation}) => {
        // NavigationUtil的一个静态变量,记录根容器StackNavigator的navigation,用来做整个App内部的跳转
        if (navigation.state.routeName === 'StackNavigator') {
            NavigationUtil.navigation = navigation;
        }
    },
});

export default SwitchNavigator;
  • 搭建AppContainer
-----------AppContainer.js-----------

import {createAppContainer} from "react-navigation";
import SwitchNavigator from './SwitchNavigator';

const AppContainer = createAppContainer(SwitchNavigator);
export default AppContainer;
  • index.jsApp.js文件、使用AppContainer

我们每创建一个RN项目,系统都默认给我们创建了两个文件,index.jsApp.js

index.js类似于我们iOS里的main.m。iOS里main.m是整个程序的入口,里面将AppDelegate作为了整个程序的代理,RN里index.js是整个程序的入口,里面将App作为了整个程序的代理。

App.js类似于我们iOS里的AppDelegate.m。iOS里我们在AppDelegate.m里设置windowrootViewControllerRN里我们在App.js里设置整个项目的根组件,即App.js文件导出的组件,就是整个项目的根组件,它位于所有组件的最下方。

所以一般情况下,我们不变动index.js文件,它代码固定为:

-----------index.js-----------

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

而是在App.js里设置整个项目的根组件:

-----------App.js-----------

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
// 导入项目的根组件
import AppContainer from './js/navigator/AppContainer';

export default class App extends Component {
    render() {
        return (
            // 设置项目的根组件
            
        );
    }
}
  • 编写项目跳转工具类
// NavigationUtil.js

/**
 * 我们专门写一个负责跳转的工具类,方便项目中跳转的统一管理
 */

export default class NavigationUtil {
    // 一个静态变量,记录根容器StackNavigator的navigation,因为项目的根容器是一个StackNavigator嘛,所以项目中的跳转都是用它的navigation
    static navigation;

    /**
     * 跳转到上一页
     */
    static goBack() {
        // 根navigation无法goBack,但是它可以pop
        this.navigation.pop();
    }

    /**
     * 跳转到指定页面
     *
     * @param page 要传递的参数
     * @param params 要跳转的页面路由名
     */
    static navigate(page, params) {
        // 但是请注意:
        // App中所有界面的跳转都是通过这个方法来跳转的,包括启动页、引导页、广告页跳转到StackNavigator,那就要想到这个时候也用StackNavigator的navigation属性做跳转能成功吗?
        // 答案是:能成功!
        // 你可以回想一下,我们只要在同一个导航栈中的界面,其实不一定非要拿栈底那个根容器的navigation属性来做跳转,其实拿其中任意一个界面的navigation属性做跳转都可以
        // 此处也是同理的,因为SwitchNavigator没有作为别人的路由存在,所以SwitchNavigator没有navigation属性,我们就只能那栈内界面的navigation属性做跳转了,那WelcomePage或者StackNavigator的navigation都行,但为了项目的统一性,我们就拿StackNavigator的了

        if (!this.navigation) {
            console.log('NavigationUtil.navigation不能为空!');
            return;
        }

        this.navigation.navigate(page, params);
    }
}

你可能感兴趣的:(RN:react-navigation搭建项目导航框架)