react-navigation導航組件使用詳解

注意了,如果有小夥伴們發現運行作者提供的react-navigation示例項目報如下的錯誤,可能是大家使用了 yarn install 命令,解決這個錯誤的辦法就是將nodemodules刪除,然後使用npm install 命令來安裝,最後使用 npm start 來起服務,應該就不報錯了,如果還有報錯,請加作者交流羣,將問題反饋到羣裏,謝謝。

RN技術總結

  • 作者React Native開源項目OneM地址(按照企業開發標準搭建框架完成開發的):https://github.com/guangqiang-liu/OneM (歡迎小夥伴們 star)
  • 作者簡書主頁:包含60多篇RN開發相關的技術文章http://www.jianshu.com/u/023338566ca5 (歡迎小夥伴們:多多關注多多點贊)
  • 作者React Native QQ技術交流羣:620792950 歡迎小夥伴進羣交流學習
  • 友情提示:在開發中有遇到RN相關的技術問題,歡迎小夥伴加入交流羣(620792950),在羣裏提問、互相交流學習。交流羣也定期更新最新的RN學習資料給大家,謝謝支持!

前言

react-navigation 組件是官方推薦使用的導航組件,功能和性能都遠遠的優於之前的Navigator組件,公司的RN項目最早是使用的react-native-router-flux導航組件,因爲那個時候react-navigation 組件還沒有出來,在使用了react-navigation後,感覺比react-native-router-flux組件有更加強大的功能,體驗也略好些,這兩個導航組件是目前star最多的導航組件,並且他們都完美的支持與Redux框架的結合使用,推薦小夥伴們兩個組件都嘗試使用下。

react-navigation官方地址

react-navigation

react-navigation Demo地址

https://github.com/guangqiang-liu/react-navigation-demo

react-navigation簡書講解地址

http://www.jianshu.com/p/5c070a302192

Demo示例講解包含三部分

  • react-navigation中最常用的基礎用法講解
  • react-navigation中StackNavigator與TabNavigator和DrawerNavigator的混合嵌套使用
  • react-navigation與Redux框架結合使用示例

Demo效果圖

 

gif

 

注意: 有小夥伴說Demo運行報錯,這裏大家需要注意,Demo clone下來之後,我們先要執行 npm install 操作, 然後在執行 react-native link,最後在 執行 npm start 來運行項目,如果還有其他的報錯信息,歡迎進羣提出報錯信息

對Redux用法不熟悉的同學們,請看作者的Redux入門講解

http://www.jianshu.com/p/faa98d8bd3fa

react-navigation 主要組成

react-navigation 組件主要由三大部分組成

  • StackNavigator:類似於iOS中的UINavigationController,頂部的導航欄,主要用於頁面間的跳轉
  • TabNavigator:類似於iOS中的UITabBarController,底部的tabBar選項卡,主要用於在同一tab下的不同頁面的切換
  • DrawerNavigator:類似於iOS中常用的抽屜功能,抽屜導航欄

下面我們對react-navigation詳解也主要圍繞這三個API來展開

StackNavigator

StackNavigator導航欄的工作原理就和iOS中原生的UINavigationController一樣的,是以棧的方式來管理每一個頁面控制器的,當使用push就是入棧,當使用pop操作時就是出棧,這個很好理解,如果我們想讓一個頁面控制器有導航欄,那麼我們首先要做的就是給這個頁面註冊導航

API

StackNavigator(RouteConfigs, StackNavigatorConfig)

StackNavigator函數中有兩個參數:

  • RouteConfigs
  • StackNavigatorConfig

配置RouteConfigs

const RouteConfigs = {
  Home: {
    screen: TabBar // screen屬性爲必須配置屬性
  },
  Home2: {
    screen: Home2,
    path:'app/Home2',
    navigationOptions: {
      title: '這是在RouteConfigs中設置的title',
      headerTitleStyle: {
        fontSize: 10
      }
    }
  },
  Home3: { screen: Home3 },
  Home4: { screen: Home4 },
  Home5: {screen: Home5},
  Home6: {screen: Home6},
  Home7: {screen: Home7},
  Setting2: {screen: Setting2},
  Setting3: {screen: Setting3},
}

配置StackNavigatorConfig

const StackNavigatorConfig = {
  initialRouteName: 'Home',
  initialRouteParams: {initPara: '初始頁面參數'},
  navigationOptions: {
    title: '標題',
    headerTitleStyle: {fontSize: 18, color: 'red'},
    headerStyle: {height: 49},
  },
  paths: 'page/main',
  mode: 'card',
  headerMode: 'screen',
  cardStyle: {backgroundColor: "#ffffff"},
  transitionConfig: (() => ({
  })),
  onTransitionStart: (() => {
    console.log('頁面跳轉動畫開始')
  }),
  onTransitionEnd: (() => {
    console.log('頁面跳轉動畫結束')
  }),
}

註冊導航

import {StackNavigator, TabNavigator} from "react-navigation"

const Navigator = StackNavigator(RouteConfigs, StackNavigatorConfig)

export default class Main extends Component {
    render() {
        return (
            <Navigator/>
        )
    }
}

從上面註冊導航的代碼塊中,我們可以看出StackNavigator函數接受兩個配置對象RouteConfigs 和 StackNavigatorConfig ,但是這裏需要注意,第二個參數StackNavigatorConfig可以省略,表示不做任何導航默認配置

StackNavigatorConfig配置參數

  • initialRouteName :導航器組件中初始顯示頁面的路由名稱,如果不設置,則默認第一個路由頁面爲初始顯示頁面
  • initialRouteParams:給初始路由的參數,在初始顯示的頁面中可以通過this.props.navigation.state.params來獲取
  • navigationOptions:路由頁面的全局配置項
  • paths :RouteConfigs裏面路徑設置的映射
  • mode :頁面跳轉方式,有card和modal兩種,默認爲 card
    • card:普通app常用的左右切換
    • modal:只針對iOS平臺,類似於iOS中的模態跳轉,上下切換
  • headerMode :頁面跳轉時,頭部的動畫模式,有 float 、 screen 、 none 三種
    • float:漸變,類似iOS的原生效果,無透明,默認方式
    • screen:標題與屏幕一起淡入淡出,如微信QQ的一樣
    • none:沒有動畫
  • cardStyle :爲各個頁面設置統一的樣式,比如背景色,字體大小等
  • transitionConfig :配置頁面跳轉的動畫,覆蓋默認的動畫效果
  • onTransitionStart :頁面跳轉動畫即將開始時調用
  • onTransitionEnd :頁面跳轉動畫一旦完成會馬上調用

在StackNavigatorConfig配置參數中有一個navigationOptions 屬性的配置,這個配置項可以理解爲導航欄的全局配置表,下面就講解這個屬性的可配置參數

navigationOptions配置參數

  • title :導航欄的標題,或者Tab標題 tabBarLabel
  • header :自定義的頭部組件,使用該屬性後系統的頭部組件會消失,如果想在頁面中自定義,可以設置爲null,這樣就不會出現頁面中留有一個高度爲64navigationBar的高度
  • headerTitle :頭部的標題,即頁面的標題
  • headerBackTitle :返回標題,默認爲 title的標題
  • headerTruncatedBackTitle :返回標題不能顯示時(比如返回標題太長了)顯示此標題,默認爲'Back'
  • headerRight :頭部右邊組件
  • headerLeft :頭部左邊組件
  • headerStyle :頭部組件的樣式
  • headerTitleStyle :頭部標題的樣式
  • headerBackTitleStyle :頭部返回標題的樣式
  • headerTintColor :頭部顏色
  • headerPressColorAndroid :Android 5.0 以上MD風格的波紋顏色
  • gesturesEnabled :否能側滑返回,iOS 默認 true , Android 默認 false

navigationOptions

// StackNavigatorConfig中的navigationOptions屬性也可以在組件內用static navigationOptions 設置(會覆蓋此處的設置)
navigationOptions: { 
        header: {  // 導航欄相關設置項
            backTitle: '返回',  // 左上角返回鍵文字
            style: {
                backgroundColor: '#fff'
            },
            titleStyle: {
                color: 'green'
            }
        },
        cardStack: {
            gesturesEnabled: true
        }
    }

注意:

  • 我們也可以在RouteConfigs 中配置 navigationOptions屬性,我們也可以在單獨頁面配置navigationOptions
  • 在頁面裏面採用靜態的方式配置 navigationOptions屬性,會覆蓋StackNavigator函數中RouteConfigs StackNavigatorConfig 對象中的navigationOptions屬性裏面的對應屬性
  • navigationOptions中屬性的優先級是:頁面中靜態配置 > RouteConfigs > StackNavigatorConfig

RouteConfigs 中配置 navigationOptions

const RouteConfigs = {
  Home: {
    screen: TabBar
  },
  Home2: {
    screen: Home2,
    path:'app/Home2',
   // 此處設置了, 會覆蓋組件內的`static navigationOptions`設置. 具體參數詳見下文
    navigationOptions: {
      title: '這是在RouteConfigs中設置的title',
      headerTitleStyle: {
        fontSize: 10
      }
    }
  },
  Home3: { screen: Home3 },
  Home4: { screen: Home4 },
  Home5: {screen: Home5},
  Home6: {screen: Home6},
  Home7: {screen: Home7},
  Setting2: {screen: Setting2},
  Setting3: {screen: Setting3},
}

在具體頁面中配置 navigationOptions

import {StackNavigator, TabNavigator} from "react-navigation"

const Navigator = StackNavigator(RouteConfigs, StackNavigatorConfig)

export default class Main extends Component {

	// 配置頁面導航選項
    static navigationOptions = {
    title: 'homeThree',
    header: (navigation, defaultHeader) => ({
      ...defaultHeader,  // 默認預設
      visible: true  // 覆蓋預設中的此項
    }),
    cardStack: {
        gesturesEnabled: false  // 是否可以右滑返回
    }
}

	// 或這樣
	static navigationOptions = {
    // title: 'Two', // 固定標題
    title: (navigation, childRouter) => {  // 動態標題
        if (navigation.state.params.isSelected) {
            return `${navigation.state.params.name}選中`;
        } else {
            return `${navigation.state.params.name}沒選中`;
        }
    },
    header: ({ state, setParams, goBack }) => {
        let right;
        if (state.params.isSelected) {
            right = (<Button title="取消" onPress={() => setParams({ isSelected: false })}/>);
        } else {
            right = (<Button title="選擇" onPress={() => setParams({ isSelected: true })}/>);
        }
        let left = (<Button title="返回" onPress={() => goBack()}/>);
        let visible = false;  // 是否顯示導航欄
        return { right, left, visible };
    },
    // header: {left: <Button title="返回"/>},
    }
    
    render() {
        return (
            <Navigator/>
        )
    }
}

TabNavigator

API

TabNavigator(RouteConfigs, TabNavigatorConfig)

從API上看,TabNavigator 和 StackNavigator 函數用法一樣,都是接受RouteConfigs和TabNavigatorConfig這兩個參數

RouteConfigs配置參數

路由配置和StackNavigator中一樣,配置路由以及對應的 screen 頁面,navigationOptions 爲對應路由頁面的配置選項

  • title :Tab標題,可用作headerTitle 和 tabBarLabel 回退標題
  • tabBarVisible :Tab的是否可見,默認爲 true
  • tabBarIcon :Tab的icon組件,可以根據 {focused: boolean, tintColor: string} 方法來返回一個icon組件
  • tabBarLabel :Tab中顯示的標題字符串或者組件,也可以根據{ focused: boolean, tintColor: string } 方法返回一個組件

配置RouteConfigs

const RouteConfigs = {
  Home: {
    screen: Home,
    navigationOptions: ({ navigation }) => ({
      tabBarLabel: 'Home',
      tabBarIcon: ({ focused, tintColor }) => (
        <Ionicons
          name={focused ? 'ios-home' : 'ios-home-outline'}
          size={26}
          style={{ color: tintColor }}/>
      )
    }),
  },
  People: {
    screen: People,
    navigationOptions: ({ navigation }) => ({
      tabBarLabel: 'People',
      tabBarIcon: ({ focused, tintColor }) => (
        <Ionicons
          name={focused ? 'ios-people' : 'ios-people-outline'}
          size={26}
          style={{ color: tintColor }}/>
      )
    }),
  },
  Chat: {
    screen: Chat,
    navigationOptions: ({ navigation }) => ({
      tabBarLabel: 'Chat',
      tabBarIcon: ({ focused, tintColor }) => (
        <Ionicons
          name={focused ? 'ios-chatboxes' : 'ios-chatboxes-outline'}
          size={26}
          style={{ color: tintColor }}/>
      )
    }),
  },
  Setting: {
    screen: Setting,
    navigationOptions: ({ navigation }) => ({
      tabBarLabel: 'Settings',
      tabBarIcon: ({ focused, tintColor }) => (
        <Ionicons
          name={focused ? 'ios-settings' : 'ios-settings-outline'}
          size={26}
          style={{ color: tintColor }}/>
      )
    }),
  }
}

TabNavigatorConfig配置參數

  • tabBarComponent : Tab選項卡組件,有TabBarBottom和TabBarTop兩個值,在iOS中默認爲 TabBarBottom ,在Android中默認爲 TabBarTop
    • TabBarTop:在頁面的頂部
    • TabBarBottom:在頁面的底部
  • tabBarPosition :Tab選項卡的位置,有top或bottom兩個值
    • top:上
    • bottom:下
  • swipeEnabled :是否可以滑動切換Tab選項卡
  • animationEnabled :切換界面是否需要動畫
  • lazy :是否懶加載頁面
  • initialRouteName :初始顯示的Tab對應的頁面路由名稱
  • order :用路由名稱數組來表示Tab選項卡的順序,默認爲路由配置順序
  • paths : 路徑配置
  • backBehavior :androd點擊返回鍵時的處理,有initialRoute 和 none 兩個值
    • initailRoute:返回初始界面
    • none:退出
  • tabBarOptions :Tab配置屬性,用在TabBarTop和TabBarBottom時有些屬性不一致
    • 用在TabBarTop時對應的屬性:
      • activeTintColor:選中的文字顏色
      • inactiveTintColor:未選中的文字顏色
      • showIcon:是否顯示圖標,默認顯示
      • showLabel:是否顯示標籤,默認顯示
      • upperCaseLabel:是否使用大寫字母,默認使用
      • pressColor:android 5.0以上的MD風格波紋顏色
      • pressOpacity:android 5.0以下或者iOS按下的透明度
      • scrollEnabled:是否可以滾動
      • tabStyle:單個Tab的樣式
      • indicatorStyle:指示器的樣式
      • labelStyle:標籤的樣式
      • iconStyle:icon的樣式
      • style:整個TabBar的樣式
    • 用在TabBarBottom時對應的屬性:
      • activeTintColor:選中Tab的文字顏色
      • inactiveTintColor:未選中Tab的的文字顏色
      • activeBackgroundColor:選中Tab的背景顏色
      • inactiveBackgroundColor:未選中Tab的背景顏色
      • showLabel:是否顯示標題,默認顯示
      • style:整個TabBar的樣式
      • labelStyle:標籤的樣式
      • tabStyle:單個Tab的樣式

使用TabBarTop代碼示例

import React, {Component} from "react";
import {StackNavigator, TabBarTop, TabNavigator} from "react-navigation";
import HomeScreen from "./index18/HomeScreen";
import NearByScreen from "./index18/NearByScreen";
import MineScreen from "./index18/MineScreen";
export default class MainComponent extends Component {
    render() {
        return (
            <Navigator/>
        );
    }
}

const TabRouteConfigs = {
    Home: {
        screen: HomeScreen,
        navigationOptions: ({navigation}) => ({
            tabBarLabel: '首頁',
        }),
    },
    NearBy: {
        screen: NearByScreen,
        navigationOptions: {
            tabBarLabel: '附近',
        },
    }
    ,
    Mine: {
        screen: MineScreen,
        navigationOptions: {
            tabBarLabel: '我的',
        },
    }
};
const TabNavigatorConfigs = {
    initialRouteName: 'Home',
    tabBarComponent: TabBarTop,
    tabBarPosition: 'top',
    lazy: true,
    tabBarOptions: {}
};
const Tab = TabNavigator(TabRouteConfigs, TabNavigatorConfigs);
const StackRouteConfigs = {
    Tab: {
        screen: Tab,
    }
};
const StackNavigatorConfigs = {
    initialRouteName: 'Tab',
    navigationOptions: {
        title: '標題',
        headerStyle: {backgroundColor: '#5da8ff'},
        headerTitleStyle: {color: '#333333'},
    }
};
const Navigator = StackNavigator(StackRouteConfigs, StackNavigatorConfigs);

使用TabBarBottom代碼示例

import React, {Component} from 'react';
import {StackNavigator, TabBarBottom, TabNavigator} from "react-navigation";
import HomeScreen from "./index18/HomeScreen";
import NearByScreen from "./index18/NearByScreen";
import MineScreen from "./index18/MineScreen";
import TabBarItem from "./index18/TabBarItem";
export default class MainComponent extends Component {
    render() {
        return (
            <Navigator/>
        );
    }
}

const TabRouteConfigs = {
    Home: {
        screen: HomeScreen,
        navigationOptions: ({navigation}) => ({
            tabBarLabel: '首頁',
            tabBarIcon: ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_homepage_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_homepage_selected_2x.png')}
                />
            ),
        }),
    },
    NearBy: {
        screen: NearByScreen,
        navigationOptions: {
            tabBarLabel: '附近',
            tabBarIcon: ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_merchant_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_merchant_selected_2x.png')}
                />
            ),
        },
    }
    ,
    Mine: {
        screen: MineScreen,
        navigationOptions: {
            tabBarLabel: '我的',
            tabBarIcon: ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_mine_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_mine_selected_2x.png')}
                />
            ),
        },
    }
};
const TabNavigatorConfigs = {
    initialRouteName: 'Home',
    tabBarComponent: TabBarBottom,
    tabBarPosition: 'bottom',
    lazy: true,
};
const Tab = TabNavigator(TabRouteConfigs, TabNavigatorConfigs);
const StackRouteConfigs = {
    Tab: {
        screen: Tab,
    }
};
const StackNavigatorConfigs = {
    initialRouteName: 'Tab',
    navigationOptions: {
        title: '標題',
        headerStyle: {backgroundColor: '#5da8ff'},
        headerTitleStyle: {color: '#333333'},
    }
};
const Navigator = StackNavigator(StackRouteConfigs, StackNavigatorConfigs);

DrawerNavigator

API

DrawerNavigator(RouteConfigs, DrawerNavigatorConfig)

DrawerNavigator StackNavigator 和 TabNavigator 函數的使用方式一樣,參數配置也類似

路由配置和StackNavigator中一樣,配置路由以及對應的 screen 頁面,navigationOptions 爲對應路由頁面的配置選項

RouteConfigs參數配置

  • title :抽屜標題,和headerTitle 、 drawerLabel一樣
  • drawerLabel :標籤字符串,或者自定義組件,可以根據{ focused: boolean, tintColor: string } 函數來返回一個自定義組件作爲標籤
  • drawerIcon :抽屜icon,可以根據 { focused: boolean, tintColor: string } 函數來返回一個自定義組件作爲icon

DrawerNavigatorConfig參數配置

  • drawerWidth :抽屜寬度,可以使用Dimensions獲取屏幕的寬度動態計算
  • drawerPosition :抽屜位置,可以是 left 或者 right
  • contentComponent :抽屜內容組件,可以自定義側滑抽屜中的所有內容,默認爲 DrawerItems
  • contentOptions :用來配置抽屜內容的屬性。當用來配置 DrawerItems 是配置屬性選項
    • items:抽屜欄目的路由名稱數組,可以被修改
    • activeItemKey:當前選中頁面的key id
    • activeTintColor:選中條目狀態的文字顏色
    • activeBackgroundColor:選中條目的背景色
    • inactiveTintColor:未選中條目狀態的文字顏色
    • inactiveBackgroundColor:未選中條目的背景色
    • onItemPress(route) :條目按下時會調用此方法
    • style:抽屜內容的樣式
    • labelStyle:抽屜的條目標題/標籤樣式
  • initialRouteName :初始化展示的頁面路由名稱
  • order :抽屜導航欄目順序,用路由名稱數組表示
  • paths :路徑
  • backBehavior :androd點擊返回鍵時的處理,有initialRoute和none兩個值
    • initailRoute:返回初始界面
    • none :退出

示例代碼

import React, {Component} from 'react';
import {DrawerNavigator, StackNavigator, TabBarBottom, TabNavigator} from "react-navigation";
import HomeScreen from "./index18/HomeScreen";
import NearByScreen from "./index18/NearByScreen";
import MineScreen from "./index18/MineScreen";
import TabBarItem from "./index18/TabBarItem";

const RouteConfigs = {
    Home: {
        screen: HomeScreen,
        navigationOptions: ({navigation}) => ({
            drawerLabel : '首頁',
            drawerIcon : ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_homepage_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_homepage_selected_2x.png')}
                />
            ),
        }),
    },
    NearBy: {
        screen: NearByScreen,
        navigationOptions: {
            drawerLabel : '附近',
            drawerIcon : ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_merchant_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_merchant_selected_2x.png')}
                />
            ),
        },
    },
    Mine: {
        screen: MineScreen,
        navigationOptions: {
            drawerLabel : '我的',
            drawerIcon : ({focused, tintColor}) => (
                <TabBarItem
                    tintColor={tintColor}
                    focused={focused}
                    normalImage={require('./img/tabbar/pfb_tabbar_mine_2x.png')}
                    selectedImage={require('./img/tabbar/pfb_tabbar_mine_selected_2x.png')}
                />
            ),
        },
    }
};

const DrawerNavigatorConfigs = {
    initialRouteName: 'Home',
    tabBarComponent: TabBarBottom,
    tabBarPosition: 'bottom',
    lazy: true,
    tabBarOptions: {}
};

const Drawer = DrawerNavigator(RouteConfigs, DrawerNavigatorConfigs)

const StackRouteConfigs = {
    Drawer: {
        screen: Drawer,
    }
};

const StackNavigatorConfigs = {
    initialRouteName: 'Drawer',
    navigationOptions: {
        title: '標題',
        headerStyle: {backgroundColor: '#5da8ff'},
        headerTitleStyle: {color: '#333333'},
    }
}

const Navigator = StackNavigator(StackRouteConfigs, StackNavigatorConfigs)

export default class Main extends Component {
    render() {
        return (
            <Navigator/>
        )
    }
}

navigation

在StackNavigator中註冊過的組件都有navigation這個屬性,navigation有5個主要參數

  • navigate
  • state
  • setParams
  • goBack
  • dispatch

我們平時使用react-navigation作爲導航組件來開發時,經常使用到的也就是這5個屬性的功能

navigate

導航到下一個頁面

<Button
            buttonStyle={{marginVertical: 10}}
            title={'跳轉到Home2界面'}
            onPress={() => this.props.navigation.navigate('Home2')}
          />
  • 導航到下一個頁面並傳遞參數
<Button
            buttonStyle={{marginVertical: 10}}
            title={'跳轉到Home3界面,並傳遞參數'}
            // 這裏傳遞了參數`id`
            onPress={() => this.props.navigation.navigate('Home3', {id: 123})}
          />

navigation中的navigate函數可以接受三個參數

  • routeName :註冊過的目標路由名稱,也就是準備跳轉到的頁面路由地址(例如上面的Home3)
  • params :跳轉到下一個頁面,傳遞的參數(例如上面的id)
  • action :下文有講到

state

state屬性包含有傳遞過來的三個參數 params、key 、routeName

  • routeName :註冊過的目標路由名稱
  • key :路由身份標識
  • params :跳轉時傳遞的參數

獲取state中的參數:this.props.navigation.state.params.id 這樣就能拿到上一個頁面傳遞的參數:id

setParams

this.props.navigation.setParams(): 該方法允許界面更改router中的參數,可以用來動態的更改導航欄的內容。比如可以用來更新頭部的按鈕或者標題等

使用場景:重寫導航按鈕的返回按鈕,自定義返回事件

export default class Home5 extends Component {

  static navigationOptions = ({navigation, screenProps}) => ({
      title: 'Home5',
      headerLeft: (
        <Button
          title={'customAction'}
          onPress={() => navigation.state.params.customAction()}
        />
      )
    }
  )

  componentDidMount() {
    const {setParams} = this.props.navigation
    setParams({customAction: () => this.tempAction()})
  }

  tempAction() {
    alert('在導航欄按鈕上調用Component內中的函數,因爲static修飾的函數爲靜態函數,內部不能使用this')
  }

  render() {
    return (
      <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
        <Text>Home5</Text>
      </View>
    )
  }
}

goBack

退出當前頁面,返回到上一個頁面,可以不傳參數,也可以傳參數,還可以傳 null

  • this.props.navigation.goBack(); // 回退到上一個頁面
  • this.props.navigation.goBack(null); // 回退到任意一個頁面
  • this.props.navigation.goBack('Home'); // 回退到Home頁面
<Button
      title={'返回上一頁面'}
      onPress={() => goBack()}
        />

dispatch

this.props.navigation.dispatch: 可以dispatch一些action,主要支持的action有一下幾種

  • Navigate
import { NavigationActions } from 'react-navigation'

  const navigationAction = NavigationActions.navigate({
    routeName: 'Profile',
    params: {},

    // navigate can have a nested navigate action that will be run inside the child router
    action: NavigationActions.navigate({ routeName: 'SubProfileRoute'})
  })
  this.props.navigation.dispatch(navigationAction)
  • Reset

Reset方法會清除原來的路由記錄,添加上新設置的路由信息, 可以指定多個action,index是指定默認顯示的那個路由頁面, 注意不要越界了

import { NavigationActions } from 'react-navigation'

  const resetAction = NavigationActions.reset({
    index: 0,
    actions: [
      NavigationActions.navigate({ routeName: 'Profile'}),
      NavigationActions.navigate({ routeName: 'Two'})
    ]
  })
  this.props.navigation.dispatch(resetAction)
  • SetParams

爲指定的router更新參數,該參數必須是已經存在於router的param中

import { NavigationActions } from 'react-navigation'

  const setParamsAction = NavigationActions.setParams({
    params: {}, // these are the new params that will be merged into the existing route params
    // The key of the route that should get the new params
    key: 'screen-123',
  })
  this.props.navigation.dispatch(setParamsAction)
  • Back
NavigationActions.back()
  • Init
const initialState = Navigator.router.getStateForAction(NavigationActions.init());

export default (state = initialState, actions) => {
    const nextState = Navigator.router.getStateForAction(actions, state);
    return nextState || state;
}

注意: 如果你的項目中使用了與Redux框架結合,這裏的dispatch就可以派發任何你想dispatch的Action了

使用場景:Counter 計數器

class Counter extends Component {

  static navigationOptions = () => ({
    title: 'Counter加減計數器'
  })

  render() {
    const {dispatch} = this.props.navigation
    return (
      <View>
        <Text>Counter</Text>
        <Text style={{marginVertical: 20, color: 'red', fontSize: 30}}>{this.props.counterValue}</Text>
        <Button
          buttonStyle={{marginVertical: 10}}
          title={'+'}
          onPress={() => dispatch(increaseAction())}
        />
        <Button
          buttonStyle={{marginVertical: 10}}
          title={'-'}
          onPress={() => dispatch(decreaseAction())}
        />
      </View>
    )
  }
}

const mapStateToProps = state => {
  return {
    counterValue: state.home.counter.counterValue
  }
}

export default connect(mapStateToProps)(Counter)

常用功能:頁面跳轉、頁面傳值、參數回調

頁面跳轉與傳值

          <Button
            buttonStyle={{marginVertical: 10}}
            title={'跳轉到Home2界面'}
            onPress={() => this.props.navigation.navigate('Home2')}
          />
          <Button
            buttonStyle={{marginVertical: 10}}
            title={'跳轉到Home3界面,並傳遞參數'}
            onPress={() => this.props.navigation.navigate('Home3', {id: 123})}
          />

在下一界面接收參數,通過this.props.navigation.state.params接收參數

export default class Home3 extends Component {

  render() {
    const {navigate} = this.props.navigation
    return (
      <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
        <Text>Home3</Text>
        <Text style={{marginVertical: 20}}>{`Home界面傳遞的參數爲:${this.props.navigation.state.params.id}`}</Text>
        <Button
          buttonStyle={{marginVertical: 10}}
          title={'跳轉到Home4界面'}
          onPress={() => navigate('Home4')}
        />
      </View>
    )
  }
}

回調傳參

當前界面進行跳轉,並傳遞參數

          <Button
            buttonStyle={{marginVertical: 10}}
            title={'跳轉到Home6界面,回調傳參'}
            onPress={() => this.props.navigation.navigate('Home6',{callback: (response) => alert(response)})}
          />

下一界面在返回之前執行函數回調傳參給上一個頁面

    const {state, goBack} = this.props.navigation

        <Button
          title={'回調傳參'}
          onPress={() => {
            state.params.callback && state.params.callback('這是回調參數')
            goBack()
          }}
        />

DeepLink

其他app或瀏覽器使用url打開次app並進入指定頁面,類似於iOS中的URL導航一樣。如瀏覽器輸入url OneM://home/home2直接進入home2頁面

iOS平臺需要額外配置

  • 在info.plist文件中設置Schemes,示例中的Schemes爲:OneM
  • 在AppDelegate添加代理函數
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
  return [RCTLinkingManager application:application openURL:url
                      sourceApplication:sourceApplication annotation:annotation];
}

在js中配置

js組件在註冊路由時設置唯一的路徑path, 例如Home2: { screen: Home2, path:'home/Home2'}

在手機瀏覽器訪問OneM://home/Home2, 彈窗選擇打開, 就可以打開OneM app並進到Home2頁面了

開發中遇到的問題及注意事項

  • 默認DrawerView不可滾動。要實現可滾動視圖,必須使用contentComponent自定義容器
{  
  drawerWidth:200, 
  contentComponent:props => <ScrollView> <DrawerItems {... props}></DrawerItems> </ ScrollView>  
}
  • 可以覆蓋導航使用的默認組件,使用DrawerItems自定義導航組件
import {DrawerItems} from 'react-navigation';  

const CustomDrawerContentComponent = (props) => (  
  <View style = {style.container}>  
    <DrawerItems {... props} />  
  </View>    
)
  • 嵌套抽屜導航 如果嵌套DrawerNavigation,抽屜將顯示在父導航下方

  • 適配頂部導航欄標題

測試中發現,在iphone上標題欄的標題爲居中狀態,而在Android上則是居左對齊。所以需要我們修改源碼,進行適配 【node_modules – react-navigation – src – views – Header.js】的326行代碼處,修改爲如下:

title: {  
   bottom: 0,  
   left: TITLE_OFFSET,  
   right: TITLE_OFFSET,  
   top: 0,  
   position: 'absolute',  
   alignItems: 'center',  
 } 

上面方法通過修改源碼的方式其實略有弊端,畢竟擴展性不好。還有另外一種方式就是,在navigationOptions中設置headerTitleStyle的alignSelf爲 ’ center ‘即可解決

  • 去除返回鍵文字顯示

【node_modules – react-navigation – src – views – HeaderBackButton.js】的91行代碼處,修改爲如下即可

{
   Platform.OS === 'ios' &&  
     title &&  
     <Text  
       onLayout={this._onTextLayout}  
       style={[styles.title, { color: tintColor }]}  
       numberOfLines={1}  
     >  
       {backButtonTitle}  
     </Text>
  } 

將上述代碼刪除即可

  • 動態設置頭部按鈕事件

當我們在頭部設置左右按鈕時,肯定避免不了要設置按鈕的單擊事件,但是此時會有一個問題,navigationOptions是被修飾爲static類型的,所以我們在按鈕的onPress的方法中不能直接通過this來調用Component中的方法。如何解決呢?在官方文檔中,作者給出利用設置params的思想來動態設置頭部標題。那麼我們可以利用這種方式,將單擊回調函數以參數的方式傳遞到params,然後在navigationOption中利用navigation來取出設置到onPress即可:

export default class Home5 extends Component {

  static navigationOptions = ({navigation, screenProps}) => ({
      title: 'Home5',
      headerRight: (
        <Button
          title={'customAction'}
          onPress={() => navigation.state.params.customAction()}
        />
      )
    }
  )

  componentDidMount() {
    const {setParams} = this.props.navigation
    setParams({customAction: () => this.tempAction()})
  }

  tempAction() {
    alert('在導航欄按鈕上調用Component內中的函數,因爲static修飾的函數爲靜態函數,內部不能使用this')
  }

  render() {
    return (
      <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
        <Text>Home5</Text>
      </View>
    )
  }
}
  • 結合BackHandler處理返回和點擊返回鍵兩次退出App效果

點擊返回鍵兩次退出App效果的需求屢見不鮮。相信很多人在react-navigation下實現該功能都遇到了很多問題,例如,其他界面不能返回。也就是手機本身返回事件在react-navigation之前攔截了。如何結合react-natigation實現呢?和大家分享兩種實現方式:

  1. 在註冊StackNavigator的界面中,註冊BackHandler
componentWillMount(){  
    BackHandler.addEventListener('hardwareBackPress', this._onBackAndroid );  
}  


componentUnWillMount(){  
    BackHandler.addEventListener('hardwareBackPress', this._onBackAndroid);  
}  

_onBackAndroid=()=>{  
    let now = new Date().getTime();  
    if(now - lastBackPressed < 2500) {  
        return false;  
    }  
    lastBackPressed = now;  
    ToastAndroid.show('再點擊一次退出應用',ToastAndroid.SHORT);  
    return true;  
} 
  1. 監聽react-navigation的Router
/** 
 * 處理安卓返回鍵 
 */  
const defaultStateAction = AppNavigator.router.getStateForAction;  
AppNavigator.router.getStateForAction = (action,state) => {  
    if(state && action.type === NavigationActions.BACK && state.routes.length === 1) {  
        if (lastBackPressed + 2000 < Date.now()) {  
            ToastAndroid.show(Constant.hint_exit,ToastAndroid.SHORT);  
            lastBackPressed = Date.now();  
            const routes = [...state.routes];  
            return {  
                ...state,  
                ...state.routes,  
                index: routes.length - 1,  
            };  
        }  
    }  
    return defaultStateAction(action,state);  
}
  • 實現Android中界面跳轉左右切換動畫

react-navigation在android中默認的界面切換動畫是上下。如何實現左右切換呢?很簡單的配置即可:

import CardStackStyleInterpolator from 'react-navigation/src/views/CardStackStyleInterpolator';  

然後在StackNavigator的配置下添加如下代碼:

transitionConfig:()=>({  
    screenInterpolator: CardStackStyleInterpolator.forHorizontal,  
}) 
  • 解決快速點擊多次跳轉

當我們快速點擊跳轉時,會開啓多個重複的界面,如何解決呢。其實在官方Git中也有提示,解決這個問題需要修改react-navigation源碼: 找到scr文件夾中的addNavigationHelpers.js文件,替換爲如下文本即可:

export default function<S: *>(navigation: NavigationProp<S, NavigationAction>) {  
  // 添加點擊判斷  
  let debounce = true;  
  return {  
      ...navigation,  
      goBack: (key?: ?string): boolean =>  
          navigation.dispatch(  
              NavigationActions.back({  
                  key: key === undefined ? navigation.state.key : key,  
              }),  
          ),  
      navigate: (routeName: string,  
                 params?: NavigationParams,  
                 action?: NavigationAction,): boolean => {  
          if (debounce) {  
              debounce = false;  
              navigation.dispatch(  
                  NavigationActions.navigate({  
                      routeName,  
                      params,  
                      action,  
                  }),  
              );  
              setTimeout(  
                  () => {  
                      debounce = true;  
                  },  
              500,  
              );  
              return true;  
          }  
          return false;  
      },  
    /** 
     * For updating current route params. For example the nav bar title and 
     * buttons are based on the route params. 
     * This means `setParams` can be used to update nav bar for example. 
     */  
    setParams: (params: NavigationParams): boolean =>  
      navigation.dispatch(  
        NavigationActions.setParams({  
          params,  
          key: navigation.state.key,  
        }),  
      ),  
  }
}

待補充問題

  • hook tabBar上點擊事件
  • Android物理返回鍵處理
  • navigator與tabBar嵌套
  • tabBar上添加badge
  • pop多層頁面
  • pop到指定頁面
  • navigator與抽屜嵌套使用
  • 導航title 在Android 平臺上不居中顯示
  • 雙擊物理鍵,退出app
  • 懶加載tabbar上數據

針對上面的待補充問題,下面來逐一解答

  • hook tabBar上點擊事件

有時我們點擊tabBar上的tab來切換頁面,但是在切換頁面之前我們想先做一些邏輯處理,然後在切換到tab頁面,這時我們就需要先hook到這個tab的點擊事件,下面代碼塊就是告訴你如何hook到tab的點擊事件,處理完事件在打開tab頁面,這個使用具體使用方式在示例Demo中都有實際使用,不清楚的同學們直接去運行示例項目瞭解即可

Chat: {
    screen: Chat,
    navigationOptions: ({ navigation }) => ({
      tabBarLabel: 'Chat',
      tabBarOnPress: () => {
        Alert.alert(
            '注意!',
            '這裏做了hook tabBar的點擊事件操作,我們可以hook到這個點擊事件,處理我們想要處理的業務後再打開 Chat這個頁面',
            [
              {text: '打開tab頁面', onPress: () => navigation.navigate('Chat')},
              {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
            ],
            { cancelable: false }
        )
      },
      tabBarIcon: ({ focused, tintColor }) => (
        <Ionicons
          name={focused ? 'ios-chatboxes' : 'ios-chatboxes-outline'}
          size={26}
          style={{ color: tintColor }}/>
      )
    }),
  },
  • Android物理返回鍵處理(待更新)

  • navigator與tabBar嵌套

navigator與tabBar嵌套 具體的結合使用方式示例Demo中有給出具體示例,這個同學們直接運行示例Demo查看即可

  • tabBar上添加badge

之前有不少同學問我,怎麼給一個tabBar設置badge,前段時間由於太忙,一直沒有去處理這個問題,後面去實現了下自定義badge,感覺還是挺簡單的,因爲navigation的tabBarItem本來就是支持自定義的,既然能夠自定義,那實現badge自然也是可行的了,下面就是具體實現代碼塊

People: {
    screen: People,
    navigationOptions: ({ navigation }) => ({
      tabBarLabel: 'People',
      tabBarIcon: ({ focused, tintColor }) => (
          <View style={{position: 'absolute', top: -10}}>
            <Ionicons
                name={focused ? 'ios-people' : 'ios-people-outline'}
                size={26}
                style={{ color: tintColor }}/>
            <View style={{backgroundColor: 'red', position: 'absolute', right: -10, top: -5, height: 15, width: 20, borderRadius: 8, overflow: 'hidden'}}>
              <Text style={{fontSize: 12, textAlign: 'center'}}>10</Text>
            </View>
          </View>
      )
    }),
  },
  • pop多層頁面

有時候我們在開發的時候,難免會遇到在點擊返回按鈕的時候,想直接返回到指定的某一個頁面,而不是返回上一級頁面,這時我們就需要對goback()函數做些處理了,具體的代碼實現如下

const Navigator = StackNavigator(RouteConfigs, StackNavigatorConfig)

const defaultStateAction = Navigator.router.getStateForAction;

Navigator.router.getStateForAction = (action, state) => {
  if (state && action.key && action.type === 'Navigation/BACK') {
    const desiredRoute = state.routes.find((route) => route.routeName === action.key)
    if (desiredRoute) {
      const index = state.routes.indexOf(desiredRoute)
      const finalState = {
        ...state,
        routes: state.routes.slice(0, index + 1),
        index: index,
      };
      return finalState
    } else {
      if (state.routes.length > action.key) {
        const stacksLength = state.routes.length - action.key
        const stacks = state.routes.slice(0, stacksLength)
        const finalState = {
          ...state,
          routes: stacks,
          index: stacksLength - 1,
        };
        return finalState
      }
    }
  }
  return defaultStateAction(action, state)
}
  • pop到指定頁面

其實goback()函數,是很容易的就可以做到返回到指定頁面,和返回指定層級的頁面的,並不像網上其他的文章說的需要改源碼啊,或者是需要結合redux才能實現啊,並不是這樣的,只需要我們簡單的維護下導航的路由棧即可解決問題,這個其實和原生iOS中處理導航的棧管理是一個道理

const Navigator = StackNavigator(RouteConfigs, StackNavigatorConfig)

const defaultStateAction = Navigator.router.getStateForAction;

Navigator.router.getStateForAction = (action, state) => {
  if (state && action.key && action.type === 'Navigation/BACK') {
    const desiredRoute = state.routes.find((route) => route.routeName === action.key)
    if (desiredRoute) {
      const index = state.routes.indexOf(desiredRoute)
      const finalState = {
        ...state,
        routes: state.routes.slice(0, index + 1),
        index: index,
      };
      return finalState
    } else {
      if (state.routes.length > action.key) {
        const stacksLength = state.routes.length - action.key
        const stacks = state.routes.slice(0, stacksLength)
        const finalState = {
          ...state,
          routes: stacks,
          index: stacksLength - 1,
        };
        return finalState
      }
    }
  }
  return defaultStateAction(action, state)
}
  • navigator與抽屜嵌套使用

navigator與抽屜嵌套使用的方式,示例Demo中已經有具體實現了,這個比較簡單,就不做詳細解答了

  • 導航title 在Android 平臺上不居中顯示

簡書上面的開發中遇到的問題及注意事項中有講解決辦法,不過作者還是建議大家將導航欄封裝成一個組件,使用自定義的組件靈活性更高

  • 雙擊物理鍵,退出app(待更新)

  • 懶加載tabbar上數據

這個懶加載Tab,這個也沒有什麼好解答的,官方已經給我提供了設置屬性,我們只需要設置個屬性即可,具體代碼如下

const TabNavigatorConfigs = {
  initialRouteName: 'Home',
  lazy: true,
  tabBarOptions: {
    activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff',
  }
}

總結

react-navigation導航組件的API相對較多,如果小夥伴們看完講解還是不清楚如何使用,建議直接運行Demo項目,邊調試邊理解。

針對之前很多同學反映出關於react-navigation 使用上遇到的一些問題,上面基本上都逐一解答了,並且都在示例Demo實戰的測試過是可行方案,後期還有其他的小夥伴遇到使用上的問題,歡迎進羣討論,或者是給我簡書留言,謝謝大家的支持。

git地址:https://github.com/aronsion/react-navigation-demo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章