React-Navigation库,Redux模块的完全状态管理,标签栏和多重导航器

原文出处 React-Navigation, complete Redux state management, tab-bar, and multiple navigators

React-Navigation库,Redux模块的完全状态管理,标签栏和多重导航器

编辑:17年2月5日更新到最新版的React-Navigation库接口,和最新版的React-Native框架

  • 发了帖才发现自己傻了,8天前那些人已经为React-Native框架发布了新一版的导航器,这个导航器可能才是最终适合Reactive-Native框架的。

GitHub主页在这里

我想写一个详细的例子来说明新版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个人资料

Dan Parker

Developer @ Fullstack Labs, super cute in 4th grade