React Native填坑之旅--Navigation篇

React Native的導航有兩種,一種是iOS和Android通用的叫做Navigator蝉绷,一種是支持iOS的叫做NavigatorIOS。我們這里只討論通用的Navigator勉耀。會了Navigator瘫想,NavigatorIOS也就不是什么難事了。

本文所使用的是React Native 0.34靡菇。FB團隊更新的太快了重归,我會在后續(xù)出現(xiàn)大的改動的時候更新本文以及代碼。

Navigator基礎

Navigator在不同的Scene之間跳轉(zhuǎn)厦凤。

  • initialRoute對象
    這是Navigator所必須的鼻吮,用于指定第一個Scene。

  • renderScene方法较鼓,這個方法必須椎木。用flow的語法來描述的話是這樣的renderScene(router: any, navigator: Navigator)违柏。renderScene方法用來根據(jù)一個給定的route來繪制Scene。如:

(route, navigator) => {
      <MySceneComponent title={route.title} navigator={navigator} />
}
  • push方法香椎,push(route: any)漱竖。Navigator使用這個方法跳轉(zhuǎn)到一個新的Scene。

API就了解這么多士鸥,下面看一個簡單的例子闲孤。數(shù)據(jù)都是寫死的。

這個例子的主要功能就是從一個Scene(組件)HomeController烤礁,跳轉(zhuǎn)到另外的一個組件PetListController讼积。就是從一組用戶里點選一個之后顯示這個用戶擁有的寵物列表。

代碼里的User數(shù)據(jù)以及用戶的Pets數(shù)據(jù)都是寫死的脚仔。如果要學習網(wǎng)絡請求方面的內(nèi)容可以參考HomeController里的fetchAction方法勤众,以及填坑系列的前篇Http篇。

準備

HomeController鲤脏,在這個組件里顯示用戶列表们颜。

import React, { Component } from 'react';
import {...略...} from 'react-native';

export default class HomeController extends Component {
    state: State;

    constructor(props) {
        super(props);

        const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
        this.state = {
            message: '',
            dataSource: ds.cloneWithRows(['Micheal', 'Jack', 'Paul'])
        };
    }

// ...略...

    render() {
        return (
            <View style={{marginTop: 64}}>
                <ListView
                    dataSource={this.state.dataSource}
                    renderRow={this._renderRow.bind(this)}
                />
            </View>
        );
    }
};

文中儲備要代碼都已經(jīng)略去。

你可以看到猎醇,數(shù)據(jù)源就是一個數(shù)組['Micheal', 'Jack', 'Paul']窥突,里面有三個人。數(shù)據(jù)最后顯示在ListView里硫嘶。

行渲染的時候阻问,在行的里面添加可以相應點擊的TouchableHighlight,在用戶點擊之后跳轉(zhuǎn)到PetListController中沦疾。

    _renderRow(data: string, sectionID: number, rowID: number, 
        highlightRow: (sectionID: number, rowID: number) => void) {
        return (
            <TouchableHighlight onPress={() => {
                    this._onPressRow(rowID);
                    highlightRow(sectionID, rowID);
                }}>
                <View style={styles.row}>
                    <Text style={styles.text}>{data}</Text>
                </View>
            </TouchableHighlight>
        );
    }

另外的一個PetListController里只是顯示某個用戶的寵物列表称近。

export default class PetListController extends Component {
    state: State;

    constructor(props) {
        super(props);

        const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });

        this.state = {
            dataSource: ds.cloneWithRows(['Dog 1', 'Dog 2', 'Dog 3'])
        };
    }

// ...略...

    render() {
        return (
            <View style={{ marginTop: 64 }}>
                <ListView
                    dataSource={this.state.dataSource}
                    renderRow={this._renderRow.bind(this)}
                    renderSeperator={this._renderSeparator.bind(this)}
                    />
            </View>
        );
    }
};

在這個組件里顯示的就是寵物數(shù)據(jù)。展示方式也是用的ListView哮塞。

開始導航

在本例中刨秆,導航開始的地方不在某個具體的Controller里(組件),而是在index.ios.js忆畅,android的在index.android.js里衡未。這么做并不好,以后重構(gòu)代碼的時候會提升到同一個文件中家凯。

我們從Navigator繪制的地方開始導航的講解:

render() {
    return (
        <View style={styles.container}>
            <Navigator
                initialRoute={this.initialRoute}
                renderScene={this._renderScene}
                navigationBar={
                    <Navigator.NavigationBar
                        routeMapper={NavigationBarRouteMapper} />
                }
                />
        </View>
    );
}

回顧一下最開始的API眠屎,renderScene方法是用來繪制每一個Scene(場景)。Sene的實質(zhì)就是一個個的組件肆饶,這個組件會占滿一個屏幕改衩。

組件的繪制需要有一些基本的信息,這個信息就是在initialRoute里指定的驯镊。

    this.initialRoute = {
        title: 'Users',
        component: HomeController,
        index: 0,
        passProps: {
            // 在這里傳遞其他的參數(shù)
        }
    }

這個initialScene是一個對象葫督,內(nèi)容有你自己定竭鞍。

下面看看Scene的繪制方法renderScene

_renderScene(route: Route, navigator: Navigator) {
    if (route.component) {
        return React.createElement(route.component
            , {...this.props, ...route.passProps, navigator, route});
    }
}

這個方法每次都會返回一個ReactElement實例,和JSX語法返回的是一樣的橄镜,雖然JSX看起來是這樣的<HomeController />偎快。

這樣寫可能對于初學者來說有一點繞,那么更加直觀一點的寫法是什么樣呢洽胶?來看看:

_renderScene(route: Route, navigator: Navigator) {
    switch(route.index) {
        case 0: 
            return <HomeController />;
        case 1:
            return <PetListController />;
        default:
            return <HomeController />;
    }
}

這個寫法作用就是根據(jù)用戶當前要訪問的Route的index值來繪制相應的組件來作為當前的Scene晒夹。

但是,如此寫法也略顯復雜姊氓。你在寫這個render方法的時候需要知道全部的導航路勁丐怯,即從一開始是哪個Scene,第二部導航到哪個Scene翔横,第三部读跷。。禾唁。以此類推效览。在Navigator路徑上有幾個Scene就需要寫幾個。所以使用第一種寫法荡短,用createElement方法來丐枉,根據(jù)指定的組件實例來返回作為Scene使用的組件。

NavigatorBar

上面的例子運行出來的時候有一個極大的問題掘托,你不注意的話在PetListController沒法返回到HomeController矛洞。

界面上并沒有返回按鈕,但是RN居然把iOS的在最左側(cè)的手勢拖動返回上一級的功能實現(xiàn)了烫映。這個功能在Android的實現(xiàn)上也有∝停總之隱藏不見的這個功能在用戶體驗上會有很大的問題锭沟。

所以必須用到Navigator的NavigationBar

<Navigator
  renderScene={(route, navigator) =>
    // ...
  }
  navigationBar={
     <Navigator.NavigationBar
       routeMapper={{
         LeftButton: (route, navigator, index, navState) =>
          { return (<Text>Cancel</Text>); },
         RightButton: (route, navigator, index, navState) =>
           { return (<Text>Done</Text>); },
         Title: (route, navigator, index, navState) =>
           { return (<Text>Awesome Nav Bar</Text>); },
       }}
       style={{backgroundColor: 'gray'}}
     />
  }
/>

NavigatorBar里設置了三個元素识补,左右兩個按鈕和中間的Title族淮。上面代碼中的按鈕無法響應用戶的點擊操作。下面就看看如何添加這部分代碼:

LeftButton: (route, navigator, index, navState) =>
  {
    if (route.index === 0) {
      return null;
    } else {
      return (
        <TouchableHighlight onPress={() => navigator.pop()}>
          <Text>Back</Text>
        </TouchableHighlight>
      );
    }
  },

理論上如的部分就看到這里凭涂。我們看看我們的代碼是怎么添加的:

var NavigationBarRouteMapper = {
    LeftButton(route, navigator, index, navState) {
        if (index > 0) {
            return (
                <TouchableHighlight style={{ marginTop: 10 }} onPress={() => {
                    if (index > 0) {
                        navigator.pop();
                    }
                } }>
                    <Text>Back</Text>
                </TouchableHighlight>
            )
        } else {
            return null
        }
    },

    RightButton(route, navigator, index, navState) {
        return null;
    },

    Title(route, navigator, index, navState) {
        return (
            <TouchableOpacity style={{ flex: 1, justifyContent: 'center' }}>
                <Text style={{ color: 'white', margin: 10, fontSize: 16 }}>
                    Data Entry
        </Text>
            </TouchableOpacity>
        );
    }
};

在左側(cè)按鈕中祝辣,首先檢查當前Scene的index是多少。如果是大于0的就說明可以回退到上一級切油,否則不作處理蝙斜。

另外,給Title也添加了響應點擊的代碼澎胡。但是只是一個效果孕荠,沒有添加onPress事件的處理代碼娩鹉。

push & pop

總結(jié)一下上面的內(nèi)容。需要跳轉(zhuǎn)的HomeController和PetListController已經(jīng)準備好了稚伍。導航用的Navigator也配置完成了弯予,并且也包括NavigationBar。在繪制每一個Secne的時候个曙,也給這些Scene傳入了props锈嫩,里面包含了Route對象和navigator對象。

有了上面的內(nèi)容只是可以在運行起來的時候顯示第一個Scene:HomeController垦搬。于是呼寸,在HomeController的ListView里的Row繪制的時候添加了TouchableHighLight并在相應事件里調(diào)用了Navigator的push方法跳轉(zhuǎn)到下一個Scene。

    _onPressRow(rowID: number) {
        this.props.navigator.push({
            title: 'Pets',
            component: PetListController,
            passProps: {}
        });
    }

push方法里傳入的對象就是Route類型(基本就是類型這個概念)悼沿。這個對象指明要跳轉(zhuǎn)的是哪個Scene等舔,以及其他信息。

pop方法在上面的NavigationBar里的左側(cè)按鈕已經(jīng)講到糟趾。

最后

要完全的實現(xiàn)Navigation慌植,需要用到Navigator和跳轉(zhuǎn)的Scene(組件)。而把他們串聯(lián)起來的是Route定義和作為props傳入每個Scene的navigator對象义郑。

代碼

代碼在這里蝶柿,可以同時支持Android和iOS。還沒有整理非驮,不過對于這個簡單的例子來說正合適交汤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市劫笙,隨后出現(xiàn)的幾起案子芙扎,更是在濱河造成了極大的恐慌,老刑警劉巖填大,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戒洼,死亡現(xiàn)場離奇詭異,居然都是意外死亡允华,警方通過查閱死者的電腦和手機圈浇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來靴寂,“玉大人磷蜀,你說我怎么就攤上這事“倬妫” “怎么了褐隆?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剖踊。 經(jīng)常有香客問我妓灌,道長轨蛤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任虫埂,我火速辦了婚禮祥山,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掉伏。我一直安慰自己缝呕,他們只是感情好,可當我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布斧散。 她就那樣靜靜地躺著供常,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸡捐。 梳的紋絲不亂的頭發(fā)上栈暇,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天誓竿,我揣著相機與錄音躁劣,去河邊找鬼慎陵。 笑死治筒,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的股缸。 我是一名探鬼主播坐漏,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼苫幢,長吁一口氣:“原來是場噩夢啊……” “哼歇僧!你這毒婦竟也來了图张?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤诈悍,失蹤者是張志新(化名)和其女友劉穎祸轮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侥钳,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡适袜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了慕趴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡鄙陡,死狀恐怖冕房,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趁矾,我是刑警寧澤耙册,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站毫捣,受9級特大地震影響详拙,放射性物質(zhì)發(fā)生泄漏帝际。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一饶辙、第九天 我趴在偏房一處隱蔽的房頂上張望蹲诀。 院中可真熱鬧,春花似錦弃揽、人聲如沸脯爪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痕慢。三九已至,卻和暖如春涌矢,著一層夾襖步出監(jiān)牢的瞬間掖举,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工娜庇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留塔次,地道東北人。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓思灌,卻偏偏與公主長得像俺叭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泰偿,可洞房花燭夜當晚...
    茶點故事閱讀 43,687評論 2 351

推薦閱讀更多精彩內(nèi)容