对于这个程序的页面导航结构,我是这样看的。首先,一个标签栏本身就是自己的导航器,而上面每个标签同样是自己的导航器。在这个例子里,我要用带有三个标签的标签栏,所以一共有四个导航器,每个导航器都有自己的还原器(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