對於這個程序的頁面導航結構,我是這樣看的。首先,一個標簽欄本身就是自己的導航器,而上面每個標簽同樣是自己的導航器。在這個例子里,我要用帶有三個標簽的標簽欄,所以一共有四個導航器,每個導航器都有自己的還原器(reducer)和狀態。
我又把代碼分成幾個“功能”,所以整個結構看起來是這樣的:
/app
/tabBar
/views
TabBarNavigation.js
navigationConfiguration.js
/tabOne
/views
TabOneNavigation.js
TabOneScreenOne.js
TabOneScreenTwo.js
navigationConfigutation.js
/tabTwo /views TabTwoNavigation.js TabTwoScreenOne.js TabTwoScreenTwo.js navigationConfiguration.js
/tabThree /views TabThreeNavigation.js TabThreeScreenOne.js TabThreeScreenTwo.js navigationConfiguration.js
store.js
標簽欄配置
先從標簽欄開始,標簽欄是程序的入口處,而且是最頂端的導航器。根據說明文檔,要構建一個標簽欄需要調用一個函數叫TabNavigator(RouteConfigs, TabNavigatorConfig)
。這個函數有兩個參數:RouteConfigs和TabNavigatorConfig,路線配置參數(RouteConfigs
)就是單個的標簽,也代表了單個的導航器。也就是說,這里每個單獨的標簽導航器都是我們給標簽欄設定的導航路線。
導航路線以鍵/值成對定義,比如ScreenName: { screen: ScreenName }
,所以路線配置參數就是一列可能要導航的路線。在這個例子里,導航路線是可能要轉到的標簽。
標簽導航設置(TabNavigatorConfig
)參數是程序接口提供的選項,可以用來自定義標簽欄。這個參數只是由幾對鍵: 值
結構組成的JS對象而已。其中最重要的一對是標簽欄選項(tabBarOptions
),靠這個選項可以設定標簽激活時與不激活時的顏色。
我整個導航配置都寫在一個獨立的JS文件里,比較簡潔,可以在其它地方引入和調用,看起來像這樣:
//'use strict' import { TabNavigator } from 'react-navigation' // Tab-Navigators import TabOneNavigation from '../tabOne/views/TabOneNavigation' import TabTwoNavigation from '../tabTwo/views/TabTwoNavigation' import TabThreeNavigation from '../tabThree/views/TabThreeNavigation'
const routeConfiguration = {
TabOneNavigation: { screen: TabOneNavigation },
TabTwoNavigation: { screen: TabTwoNavigation },
TabThreeNavigation: { screen: TabThreeNavigation },
}
const tabBarConfiguration = { //...other configs tabBarOptions:{ // tint color is passed to text and icons (if enabled) on the tab bar activeTintColor: 'white', inactiveTintColor: 'blue', // background color is for the tab component activeBackgroundColor: 'blue', inactiveBackgroundColor: 'white', } }
export const TabBar = TabNavigator(routeConfiguration,tabBarConfiguration)
完成標簽欄配置
標簽欄構建好之后,還沒有成型,不能進行實際渲染。我們還要設置好每個標簽導航器,使它們也能正確地渲染屏幕顯示。這些導航器我們稱為棧導航器(
StackNavigators
)。
棧導航器配置
根據說明文檔,要構建一個棧導航器,需要調用一個和TabNavigator
很類似的函數叫StackNavigator(RouteConfigs, StackNavigatorConfig)
,同樣有兩個參數,只是這個函數的參數里配置更多。參看說明文檔來獲取更多信息。
簡單地配置設定一下,和標簽欄配置差不多:
//'use strict' import { StackNavigator } from 'react-navigation' // Screens import TabOneScreenOne from './views/TabOneScreenOne' import TabOneScreenTwo from './views/TabOneScreenTwo' const routeConfiguration = { TabOneScreenOne: { screen: TabOneScreenOne }, TabOneScreenTwo: { screen: TabOneScreenTwo }, } // going to disable the header for now const stackNavigatorConfiguration = { headerMode: 'none', initialRouteName: 'TabOneScreenOne' } export const NavigatorTabOne = StackNavigator(routeConfiguration,stackNavigatorConfiguration) //只要改個名,就把三個標簽都設置好了。創建一個簡單的屏幕組件來渲染
import React from 'react' import { View, Text } from 'react-native' export default class TabOneScreenOne extends React.Component { render(){ return( <View style={{ flex:1, backgroundColor:'red', alignItems:'center', justifyContent:'center' }}> <Text>{ 'Tab One Screen One' }</Text> </View> ) } }
完成棧導航器配置
是時候把所有的部分都串起來了
配置redux-store實例
有一個很有用的輔助函數叫getStateForAction
,它和路由掛鈎,並處理所有的導航邏輯。
這個函數在Redux store
實例中這樣用:
//'use strict' // Redux import { applyMiddleware, combineReducers, createStore } from 'redux' import logger from 'redux-logger' // Navigation import { NavigatorTabOne } from './tabOne/navigationConfiguration' import { NavigatorTabTwo } from './tabTwo/navigationConfiguration' import { NavigatorTabThree } from './tabThree/navigationConfiguration' import { TabBar } from './tabBar/navigationConfiguration' // Middleware const middleware = () => { return applyMiddleware(logger()) } export default createStore( combineReducers({ tabBar: (state,action) => TabBar.router.getStateForAction(action,state), tabOne: (state,action) => NavigatorTabOne.router.getStateForAction(action,state), tabTwo: (state,action) => NavigatorTabTwo.router.getStateForAction(action,state), tabThree: (state,action) => NavigatorTabThree.router.getStateForAction(action,state), }), middleware(), )
// React import React from 'react' import { AppRegistry } from 'react-native' // Redux import { Provider } from 'react-redux' import store from './app/store' // Navigation import TabBarNavigation from './app/tabBar/views/TabBarNavigation' class SampleNavigation extends React.Component { render(){ return( <Provider store={store}> <TabBarNavigation /> </Provider> ) } } AppRegistry.registerComponent('SampleNavigation', () => SampleNavigation)
與標簽欄掛鈎
還記得本文一開始,我們創建了一些參數,然后傳入一個函數來創建標簽欄嗎?要將導航控制權從react-navigation
庫轉移到Redux State
實例中去,我們需要給創建出來的標簽欄提供導航狀態,再用react-navigation
庫擁有的輔助函數來分派出去。為標簽欄建立的文件像這樣:
// React import React from 'react' // Navigation import { addNavigationHelpers } from 'react-navigation' import { TabBar } from '../navigationConfiguration' //Redux import { connect } from 'react-redux' const mapStateToProps = (state) => { return { navigationState: state.tabBar, } } class TabBarNavigation extends React.Component { render(){ const { dispatch, navigationState } = this.props return ( <TabBar navigation={ addNavigationHelpers({ dispatch: dispatch, state: navigationState, }) } /> ) } } export default connect(mapStateToProps)(TabBarNavigation)
將棧導航器與每個獨立的標簽掛鈎
基本上和標簽欄一樣的方法。
import React from 'react' // Navigation import { addNavigationHelpers } from 'react-navigation' import { NavigatorTabOne } from '../navigationConfiguration' // Redux import { connect } from 'react-redux' // Icon import Icon from 'react-native-vector-icons/FontAwesome' const mapStateToProps = (state) => { return { navigationState: state.tabOne } } class TabOneNavigation extends React.Component { render(){ const { navigationState, dispatch } = this.props return ( <NavigatorTabOne navigation={ addNavigationHelpers({ dispatch: dispatch, state: navigationState }) } /> ) } } export default connect(mapStateToProps)(TabOneNavigation)
這樣應該能生成程序,運行程序並且在程序中導航了。但不怎么好看
讓我們去掉那些難看的文字,加些iOS系統圖標吧。
要改變標簽文字,加上圖標,只要把static navigationOptions
聲明放在各自的標簽導航器里就行了。記得在標簽欄配置里,我們設置了tintColors
顏色,現在就可以用這些顏色了。
第一個標簽導航器:
// React import React from 'react' // Navigation import { addNavigationHelpers } from 'react-navigation' import { NavigatorTabOne } from '../navigationConfiguration' // Redux import { connect } from 'react-redux' // Icon import Icon from 'react-native-vector-icons/FontAwesome' const mapStateToProps = (state) => { return { navigationState: state.tabOne } } class TabOneNavigation extends React.Component { static navigationOptions = { tabBarLabel: 'Tab One', tabBarIcon: ({ tintColor }) => <Icon size={ 20 } name={ 'cogs' } color={ tintColor }/> } render(){ const { navigationState, dispatch } = this.props return ( <NavigatorTabOne navigation={ addNavigationHelpers({ dispatch: dispatch, state: navigationState }) } /> ) } } export default connect(mapStateToProps)(TabOneNavigation)
看起來不錯。
現在我們來處理標簽內部之間的導航。我要在每個標簽的第一屏加一個按鈕,能導航到新的路線上去。
標簽一,屏幕一:
'use strict' import React from 'react' import { View, Text, TouchableOpacity } from 'react-native' export default class TabOneScreenOne extends React.Component { render(){ return( <View style={{ flex:1, backgroundColor:'red', alignItems:'center', justifyContent:'center' }}> <Text>{ 'Tab One Screen One' }</Text> <TouchableOpacity onPress={ () => this.props.navigation.navigate('TabOneScreenTwo') } style={{ padding:20, borderRadius:20, backgroundColor:'yellow', marginTop:20 }}> <Text>{'Go to next screen this tab'}</Text> </TouchableOpacity> </View> ) } }
標簽一,屏幕二:
use strict' import React from 'react' import { View, Text, TouchableOpacity } from 'react-native' export default class TabOneScreenTwo extends React.Component { render(){ return( <View style={{ flex:1, backgroundColor:'orange', alignItems:'center', justifyContent:'center' }}> <Text>{ 'Tab One Screen Two' }</Text> <TouchableOpacity onPress={ () => this.props.navigation.goBack() } style={{ padding:20, borderRadius:20, backgroundColor:'purple', marginTop:20 }}> <Text>{'Go back a screen'}</Text> </TouchableOpacity> </View> ) } }
現在所有的導航狀態都儲存在redux store實例中了。
有了這個信息,就可以相當方便並任意地處理安卓系統回退鍵(AndroidBack
)行為了。
如果想讓后退按鈕回到某一標簽的某一屏幕,只要加一個偵聽器即可。
BackHandler.addEventListener('hardwareBackPress', this.backAction ) backAction = () => { // get the tabBar state.index to see what tab is focused // get the individual tab's index to see if it's at 0 or if there is a screen to 'pop' if (you want to pop a route) { // get the navigation from the ref const { navigation } = this.navigator.props // pass the key of the focused route into the goBack action navigation.goBack(navigation.state.routes[navigation.state.index].key) return true } else { return false } } <TabWhateverNavigator ref={ (ref) => this.navigator = ref } navigation={ addNavigationHelpers({ dispatch: dispatch, state: navigationState }) } />
自定義行為/還原器/路由/不管叫什么
想不想通過屏幕上的按鈕跳轉至標簽呢?我想的。這里有一個方法:
將getStateForAction
函數放到navigationConfiguration
文件里去,這樣更好看些。讓它攔截自定義行為或只是將同一個函數返回。
像這樣tabBar => navigationConfiguration,我這個例子目的就達到了
export const tabBarReducer = (state, action) => { if (action.type === 'JUMP_TO_TAB') { return { ...state, ...action.payload } } else { return TabBar.router.getStateForAction(action,state) } }
標簽三的屏幕上有一個按鈕
<TouchableOpacity onPress={ () => this.props.navigation.dispatch({ type:'JUMP_TO_TAB', payload:{index:0} }) } style={{ padding:20, borderRadius:20, backgroundColor:'deeppink', marginTop:20 }}> <Text>{'jump to tab one'}</Text> </TouchableOpacity>
還有新的store
實例
//...stuff and things import { TabBar, tabBarReducer } from './tabBar/navigationConfiguration' // more things export default createStore( combineReducers({ //...other stuff and more things tabBar: tabBarReducer, }), middleware(), )
原文:http://www.zcfy.cc/article/react-navigation-complete-redux-state-management-tab-bar-and-multiple-navigators-4449.html