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閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容