注意了,如果有小夥伴們發現運行作者提供的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 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效果圖
注意: 有小夥伴說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標題 tabBarLabelheader
:自定義的頭部組件,使用該屬性後系統的頭部組件會消失,如果想在頁面中自定義,可以設置爲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的是否可見,默認爲 truetabBarIcon
: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時對應的屬性:
使用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 或者 rightcontentComponent
:抽屜內容組件,可以自定義側滑抽屜中的所有內容,默認爲 DrawerItemscontentOptions
:用來配置抽屜內容的屬性。當用來配置 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實現呢?和大家分享兩種實現方式:
- 在註冊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;
}
- 監聽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實戰的測試過是可行方案,後期還有其他的小夥伴遇到使用上的問題,歡迎進羣討論,或者是給我簡書留言,謝謝大家的支持。