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}) => (
                <MaterialIcons
                    name={'whatshot'}
                    size={26}
                    style={{color: tintColor}}
                />
            ),
        },
    },
    TrendingPage: {
        screen: TrendingPage,
        navigationOptions: {
            tabBarLabel: '趋势',
            tabBarIcon: ({tintColor}) => (
                <Ionicons
                    name={'md-trending-up'}
                    size={26}
                    style={{color: tintColor}}
                />
            ),
        },
    },
    FavoritePage: {
        screen: FavoritePage,
        navigationOptions: {
            tabBarLabel: '收藏',
            tabBarIcon: ({tintColor}) => (
                <MaterialIcons
                    name={'favorite'}
                    size={26}
                    style={{color: tintColor}}
                />
            ),
        },
    },
    MyPage: {
        screen: MyPage,
        navigationOptions: {
            tabBarLabel: '我的',
            tabBarIcon: ({tintColor}) => (
                <Entypo
                    name={'user'}
                    size={26}
                    style={{color: 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可能要操作很多界面,都配置在这里让这个文件显得有点累赘,所以我们就去相应的界面里配置它了,而不在这里配置
        //      <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<Props> {
    render() {
        return (
            // 设置项目的根组件
            <AppContainer/>
        );
    }
}
  • 编写项目跳转工具类
// 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);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容