DaoG

React Native Navigator-像专家一样处理React Native 导航

DaoG · 2017-05-17翻译 · 1611阅读 原文链接

React Native Navigator可以做很多事情。在这里,我将尝试介绍几个可以使用导航器做的例子,并深入解释它们的工作原理。

在React Native中,你目前有两个能开箱即用且稳定的导航方案(其中只有一个支持跨平台)。还有即将到来的Navigator实验室,它还不稳定,但将会是Navigator的替代品。如果您有兴趣了解更多信息,请查看我的教程。

我们将重点讨论Navigator组件,因为我看到的大多数问题都与其有关,同时它也是目前的跨平台导航器选择方案。我们不会讨论NavigatorIOS,因为当前并不是主要的 react native 维护项目,并且只能在 iOS 上使用。

以下是社区中其他几个不错的导航方案:

  1. ExNavigator by Exponent
  2. React Native Navigator
  3. React Native Router Flux
  4. React Native Simple Router

第1部分 - 基本场景渲染

理解场景渲染的关键是,无论你传任何东西到

navigator.push

都会在renderScene方法中以路由属性进行调用。

我们来看一个例子:

如下创建一个导航器

<Navigator
  style={{ flex:1 }}
  initialRoute={{ name: 'Main' }}
  renderScene={ this.renderScene } />

然后,您的renderScene方法如下所示:

renderScene(route, navigator) {
   if(route.name == 'Main') {
     return <Main navigator={navigator} />
   }
   if(route.name == 'Home') {
     return <Home navigator={navigator} />
   }
},

当你准备要push一个新路由时,可以这样做:

_navigate(){
  this.props.navigator.push({
    name: 'Home', **// Matches route.name**
  })
}

调用它,只需简单使用:

<TouchableHighlight onPress={ () => this._navigate() }>
    <Text>GO To View</Text>
</TouchableHighlight>

传递属性

那么如何传递属性?回顾上面的例子,现在我们将给Navigator加入传递属性的功能。

我们需要创建一个对象,以此来传递属性。许多人习惯将这个对象命名为passProps,当然你也可以按自己喜好命名它。以passProps命名是因为这个名字能见名知义。我们的navigator组件将统一使用这个命名方式。首先,我们来修改之前的renderScene方法:

renderScene(route, navigator) {
   if(route.name == 'Main') {
     return <Main navigator={navigator} {...route.passProps} />
   }
   if(route.name == 'Home') {
     return <Home navigator={navigator} {...route.passProps} />
   }
},

接下来,轮到_navigate函数:

_navigate(property){
  this.props.navigator.push({
    name: 'Home',
    passProps: {
      name: property
    }
  })
}

最后我们给navigator传入些属性:

<TouchableHighlight onPress={ () => this._navigate('Hello World') }>
    <Text>GO To View</Text>
</TouchableHighlight>

这里有个例子。点我查看

当你的应用包含了大量怼路由时,之前我们所写的renderScene方法会膨胀得比较庞大。接下来,我们会改造renderScene方法使更动态灵活。

记住:你传入到navigator.push里的任何东西都可以作为路由上的一个属性来使用.

这表示如果你这样调用navigator.push:

this.props.navigator.push({
  name: 'chris', 
  animal: 'dog',
  children: 6, 
  state: 'Arizona'
})

以上所有这些属性都可以在renderScene方法中以路由属性的方式调用:

renderScene(route, navigator) {
  route.name === 'chris' // true
  route.animal === 'cat' // false
  route.children === 7 // false
  route.state === 'Arizona' // true
}

那么你可以像这样算出有哪些属性被传递了进来:

if(route.animal == 'cat') // do something, render a certain scene or something

第2部分 - 动态场景渲染

这里我们将用另一种姿势来创建renderScene方法:

renderScene(route, navigator) {
   return React.createElement(route.component, { ...this.props, ...route.passProps, route, navigator } )
}

…this.props —用扩展语法 确保所有的属性都可以如常用的做法那样通过this.props传递

... route.passProps - 当推向路由时,允许passProps属性传递到下一组件

route - route作为组件上的一个可用属性

navigator - navigator作为组件上的一个可用属性

还有另一种方法来渲染上述配置,虽然我们不会在我们的示例中使用它,但是如果您不熟悉或不想使用React.createElement,那么了解如何使用该方法是很有益处的,因为它具有更容易阅读的语法:

renderScene(route, navigator) {
    let RouteComponent = route.component
    return <RouteComponent navigator={navigator} {...route.passProps} />
}

或:

renderScene(route, navigator) {
  return <route.component navigator={navigator} {...route.passProps} />
}

如上面第一个动态示例所示,我们使用React.createElement函数来手动创建组件定义。

下面是React Native中React.createElement的api:

ReactElement.createElement(component, [object props], [children …] )

> 第一个参数是要渲染的组件,第二个参数是包含任何需要附加到该组件的props的对象,第三个参数是任何子节点。

为了使用这个API,我们将这样调用navigator.push:

_navigate(name) {
  this.props.navigator.push({
    component: Home,
    passProps: {
      name: name
    }
  })
}

并以下所示的方式来传递属性:

<TouchableHighlight onPress={ () => this._navigate('SOME NAME') }>
  <Text>GO To Home</Text>
</TouchableHighlight>

组件必须在视图的作用域内可用时才能正常工作。您也可以将视图导入页面内:

import Home from '../pathtohome'

或者在同一个文件中创建组件。

这里有一个上述配置的例子

第3部分 - 场景配置

您可以使用navigator上的configureScene方法来控制如何渲染视图。可用的属性有:

PushFromRight
FloatFromRight
FloatFromLeft
FloatFromBottom
FloatFromBottomAndroid
FadeAndroid
HorizontalSwipeJump
HorizontalSwipeJumpFromRight
VerticalUpSwipeJump
VerticalDownSwipeJump

要实现这一点,您可以使用此附加属性创建navigator,并将其附加到一个函数中:

<Navigator
 **configureScene={ this.configureScene }**    style={{ flex:1 }}
    initialRoute={{ component: Main }}
    renderScene={ this.renderScene } />

然后,设置您的configureScene方法并传递到上面所列出的某一个配置中:

configureScene(route, routeStack){
   return Navigator.SceneConfigs.PushFromRight 
}

使用这种方法的一个有趣的例子是,首先检查路由上怼属性,然后显示模态,同时无需执行大量配置:

configureScene(route, routeStack){
    if(route.type === 'Modal') {
      return Navigator.SceneConfigs.FloatFromBottom
    }
    return Navigator.SceneConfigs.PushFromRight 
}

现在,我们只需要将类型传递给navigator,并设置一个默认类型:

_navigate(name, type='Normal') {
  this.props.navigator.push({
    component: Home,
    passProps: {
      name: name
    },
    type: type
  })
}

如果_navigate调用中没有定义类型,那么type的默认值就是'Normal'。如果其他任何东西作为_navigate的第二个参数传递,“Normal”类型将被相应地替换。

我们可以像这样调用模态:

<TouchableHighlight 
  onPress={ () => this._navigate('HELLO!!', 'Modal') }>
  <Text style={ styles.buttonText }>Show Modal</Text>
</TouchableHighlight>

然后!这里有个能运行的例子

第4部分 - 自定义导航栏

可能你想要一个永久性的导航栏。为此,您需要将另一个名为navigationBar的属性添加到Navigator组件,然后传入到组件内:

navigationBar={
  <Navigator.NavigationBar 
    style={ styles.nav } 
    routeMapper={ NavigationBarRouteMapper } />
}

然后我们设置NavigationBarRouteMapper对象来配置导航栏。NavigationBarRouteMapper具有三个函数参数:

LeftButton(route, navigator, index, navState) { 
  // some component or null 
}

RightButton(route, navigator, index, navState) { 
  // some component or null 
}

Title(route, navigator, index, navState) { 
  // some component or null 
}

我们已经覆盖了路由和导航属性,并且在renderScene方法中,可以使用与路由和导航中相同的属性。无论你的路由堆栈有多深,索引都帮你维护了一份记录。索引从0开始并向上递增,在你需要的时候,可以根据索引来进行计算。

我们将检查索引是否为零,以显示或隐藏后退按钮。

我们还会检查是否有一个叫route.onPress函数传入,以此来用自定义函数和文本显示或隐藏右按钮:

var NavigationBarRouteMapper = {
  LeftButton(route, navigator, index, navState) {
    if(index > 0) {
      return (
        <TouchableHighlight
          underlayColor="transparent"
          onPress={() => { if (index > 0) { navigator.pop() } }}>
          <Text style={ styles.leftNavButtonText }>Back</Text>
        </TouchableHighlight>)
    } 
    else { return null }
  },
  RightButton(route, navigator, index, navState) {
    if (route.onPress) return (
      <TouchableHighlight
         onPress={ () => route.onPress() }>
         <Text style={ styles.rightNavButtonText }>
              { route.rightText || 'Right Button' }
         </Text>
       </TouchableHighlight>)
  },
  Title(route, navigator, index, navState) {
    return <Text style={ styles.title }>MY APP TITLE</Text>
  }
};

接下来,我们设置我们的.push配置,创建我们的onPress,并将onPress方法作为路由的属性传递:

onPress() {
  alert("YO FROM RIGHT BUTTON")
}

gotoNext() {
  this.props.navigator.push({
    component: Two,
    passProps: {
      id: 'MY ID',
    },
    onPress: this.onPress,
    rightText: 'ALERT!'
  })
}

这里有个例子的实际demo:

这里是个导航栏的示例。

第5部分 - 导航方法

到目前为止,我们一直在使用导航器的push方法:

this.props.navigator.push({ 
  component: SomeComponent 
})

但是,还有很多其他方法:

getCurrentRoutes() - returns the current list of routes

jumpBack() - Jump backward without unmounting the current scene

jumpForward() - Jump forward to the next scene in the route stack

jumpTo(route) - Transition to an existing scene without unmounting

push(route) - Navigate forward to a new scene, squashing any scenes that you couldjumpForward to

pop() - Transition back and unmount the current scene

replace(route) - Replace the current scene with a new route

replaceAtIndex(route, index) - Replace a scene as specified by an index

replacePrevious(route) - Replace the previous scene

resetTo(route) - Navigate to a new scene and reset route stack

immediatelyResetRouteStack(routeStack) - Reset every scene with an array of routes

popToRoute(route) - Pop to a particular scene, as specified by its route. All scenes after it will be unmounted

popToTop() - Pop to the first scene in the stack, unmounting every other scene

这里是如何使用这些方法的例子:

this.props.navigator.replace({ 
  component: SomeComponent 
})

this.props.navigator.pop()

this.props.navigator.popToTop()

this.props.navigator.resetTo({ 
  component: SomeComponent
})

我已经用这些方法做了一个项目

React Native可能会在导航中进行未来的更改,因为有人提议替换掉目前的API和实现。在这种情况下,如果这个提案被实现了,那么我将在基于未来的新导航分享一个教程!

相关文章