React-Navigation库,Redux模块的完全状态管理,标签栏和多重导航器
编辑:17年2月5日更新到最新版的React-Navigation库接口,和最新版的React-Native框架
- 发了帖才发现自己傻了,8天前那些人已经为React-Native框架发布了新一版的导航器,这个导航器可能才是最终适合Reactive-Native框架的。
我想写一个详细的例子来说明新版react-navigation库的程序接口,它使用redux模块来为导航器管理状态。
第一步:初始化一个新的项目名叫react-native init SampleNavigation
第二步:安装依存组件。我要用react-native-vector-icons模块来为标签栏添加图标。
npm install --save redux react-redux redux-logger react-navigation react-native-vector-icons && react-native link
第三步:写代码。
对于这个程序的页面导航结构,我是这样看的。首先,一个标签栏本身就是自己的导航器,而上面每个标签同样是自己的导航器。在这个例子里,我要用带有三个标签的标签栏,所以一共有四个导航器,每个导航器都有自己的还原器(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)
只要改个名,就把三个标签都设置好了。创建一个简单的屏幕组件来渲染
'use strict'
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(),
)
索引
'use strict'
// 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库拥有的辅助函数来分派出去。为标签栏建立的文件像这样:
'use strict'
// 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)
将栈导航器与每个独立的标签挂钩
基本上和标签栏一样的方法。
'use strict'
// 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 {
render(){
const { navigationState, dispatch } = this.props
return (
<NavigatorTabOne
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState
})
}
/>
)
}
}
export default connect(mapStateToProps)(TabOneNavigation)
这样应该能生成程序,运行程序并且在程序中导航了。但不怎么好看
让我们去掉那些难看的文字,加些iOS系统图标吧。
要改变标签文字,加上图标,只要把static navigationOptions声明放在各自的标签导航器里就行了。记得在标签栏配置里,我们设置了tintColors颜色,现在就可以用这些颜色了。
第一个标签导航器:
'use strict'
// 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(),
)
真正实现轻松导航,想去哪就去哪,想从哪出发就从哪出发
支持我们吧
有多感谢Dan Parker给大家带来的故事就拍多少手。
37932* BlockedUnblockFollowFollowing
Dan Parker
Developer @ Fullstack Labs, super cute in 4th grade