3. React Navigation 导航器路由

3,760 阅读7分钟

React Navigationstack navigator 为应用提供了一种在屏幕之间切换并管理导航历史记录的方式。 如果应用程序只使用一个 stack navigator ,则它在概念上类似于Web浏览器处理导航状态的方式 - 当用户与它进行交互时,应用程序会从导航堆栈中新增和删除页面,这会导致用户看到不同的页面。React Navigation提供了在堆栈中的路由之间导航时的手势和动画。

安装React Navigation

  1. 进入RN组件目录,安装react-navigation

    yarn add react-navigation
    
  2. 添加react-navigation的依赖库react-native-gesture-handler

    yarn add react-native-gesture-handler
    
  3. 添加导航器子组件react-navigation-stack

    yarn add react-navigation-stack
    
  4. 如果要创建tab导航控制器,添加react-navigation-tabs

    yarn add react-navigation-tabs
    
  5. 进入iOS目录,执行pod install安装react-navigation依赖库

  6. 如果是在原生项目中集成此库,需要在添加上述库之后另外手动添加RNGestureHandler库:打开Podfile,添加pod 'RNGestureHandler', :path => "../node_modules/react-native-gesture-handler",执行pod install。该子库在纯RN项目中集成本导航库时,在ios目录中执行pod install会自行添加,而在将RN集成到原生iOS项目中时不会自行添加。

创建 Stack Navigator

// 定义Home页面
class HomeScreen extends React.Component {
  render() {
    return (
      <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
        <Text>Home Screen</Text>
      </View>
    )
  }
}

// 创建导航器
const AppNavigator = createStackNavigator({
  Home: HomeScreen,
});

// 创建以导航器作为根组件的容器
const AppContainer = createAppContainer(AppNavigator);

// 程序根部入口
export default class App extends React.Component {
  render() {
    return <AppContainer />;
  }
}

创建多个路由

const AppNavigator = createStackNavigator(
  {
    Home: HomeScreen,
    Details: DetailsScreen,
  },
  {
    initialRouteName: 'Home',	// 指定初始化页面
  }
);

页面切换

跳转

class HomeScreen extends React.Component {
  render() {
    return (
      <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
        <Text>Home Screen</Text>
        // 添加一个按钮,点击事件为跳转到Profile页
        <Button 
          title = "Go to Profile"
          onPress = {() => this.props.navigation.navigate('Profile')}
        />
      </View>
    )
  }
}
  1. this.props.navigation: 此属性会被传递到处于导航器栈中的每一个页面组件
  2. navigate('Profile'): Profile必须是事先已经在navigator中被定义过的路由名称,否则调用此方法不会有任何效果
  3. 如果当前页面已处于某个路由页面,在该页面通过navigate再次调用该页面路由,不会有任何效果,导航器会认为当前页已经处于该路由页面而不再执行跳转。如果需要再次跳转到当前路由相同的页面,需使用push函数。
class ProfileScreen extends React.Component {
  render() {
    return (
      <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
        <Text>Profile Screen</Text>
        <Button 
          title = "Go to Profile again."
          // 已处于Profile页再通过navigate跳转Profile不会再执行跳转
          // onPress = {() => this.props.navigation.navigate('Profile')}
          // 需调用push函数才会再一次跳转到Profile页
          onPress = {() => this.props.navigation.push('Profile')}
        />
      </View>
    )
  }
}

: 每一次调用push都会压入一个新页面,而调用navigate导航器会先查找该路由页面是否已存在然后决定是否压入新页面,如不存在,则压入新页面,否则直接导航到该页。因此,可以通过navigate来跳转到已经存在的页面。

返回

当当前页存在上一级路由时,导航栏会自带默认的返回按钮。如需手动调用返回,可以通过goBack()函数实现。

如果需要返回到栈顶页面,可以使用函数popToTop()(相当于OC中的popToRootViewController)


页面的生命周期

原理类似于iOS控制器的生命周期。

相关函数:

componentDidMount();
componentDidUpdate(prevProps);
componentWillUnmount();

路由传参

navigate(routeName, Params)

将参数包装成对象,作为navigate/push的第二个参数传递,在路由页面中通过this.props.navigation获取参数。

相关获取参数api:

getParam(key, defautValue); // 获取指定key的值,如果不存在,则采用 defaultValue
.state.params		// 获取整个参数对象
// 通过state.params获取整体参数
var greeting = this.props.navigation.state.params.sayHello;
alert(greeting)

// 通过getParam获取单个key对应的参数,如果不存在,使用默认值
const { navigation } = this.props;
var greeting = navigation.getParam('sayHello', 'NO-Greeting');
alert(greeting);

配置导航栏

设置导航栏标题

页面组件拥有一个静态的navigationOptions属性,可以是一个对象,或者是一个返回包含多种属性配置的对象的函数。

静态设置标题:
  static navigationOptions = {
    title: 'Home'
  }
使用函数设置标题
  static navigationOptions = ({ navigation }) => {
    return {
      // 相当于对title与params中的'header'做了映射,如需修改,需要传入'header'参数才可生效
      title: navigation.getParam('header', '个人中心')
    }
  }

此函数默认会传递一个包含三条属性的对象,分别为:

  • navigation: 屏幕组件的navigation属性,相当于this.props.navigation
  • screenProps: 导航器组件上层传递过来的参数
  • navigationOptions: 未提供新值时采用的旧值或默认值
更新标题

this.props.navigation.setParams()

setParams传入一个对象,其中需要修改的标题内容的key,需要与navigationOptions函数返回对象中标题对应的key保持一致。例如:

  <Button
    title="Update the title"
    // 此处需使用与上面通过函数设置标题中采用的key保持一致才可以生效
    onPress={() => this.props.navigation.setParams({ header: '更新标题' })}
  />

修改导航栏样式

三条属性用来修改导航栏样式:

  • headerStyle: 用于修改导航栏背景视图样式
  • headerTintColor: 修改返回按钮与标题文字颜色
  • headerTitleStyle: 修改导航栏标题文字样式,包括字体、字号及其他文字样式等
  headerStyle: {
    backgroundColor: 'purple'
  },
  headerTintColor: '#fff',
  headerTitleStyle: {
    fontWeight: 'bold',
  }

导航栏状态并不是像iOS中是全局共享的,但可以在各个屏幕组件之间共享导航栏参数设置。

跨屏幕共享公共导航栏配置

可以在创建路由导航器时,配置导航栏的默认样式,保证全局导航栏样式的统一。任何属于该导航器管理下的页面组件均会继承默认导航栏样式属性。

const AppNavigator = createStackNavigator(
  {
  Home: HomeScreen,
  Profile: ProfileScreen
  }, 
  {
    initialRouteName: 'Home',
    // 设置全局默认导航栏样式
    defaultNavigationOptions: {
      headerStyle: {
        backgroundColor: '#f4511e',
      },
      headerTintColor: '#fff',
      headerTitleStyle: {
        fontWeight: 'bold',
      },
    },
  }
);

创建路由导航器时,navigationOptions可用于配置导航器自身的属性。

  // 设置自身作为tab控制器的组件是的tabBar名称
  navigationOptions: {
    tabBarLabel: 'ssss!',
  },
重写默认导航栏属性

页面组件的导航栏样式属性在页面被展示时会和从导航器共享的导航栏样式属性进行合并,页面组件的自身样式具有更高的优先级。因此可以对个别页面做导航栏个性化设置。

  static navigationOptions = ({ navigation, navigationOptions }) => {
    return {
      title: navigation.getParam('header', '个人中心'),
      // 将文本颜色与背景色与默认样式进行对调
      headerStyle: {
        backgroundColor: navigationOptions.headerTintColor,
      },
      headerTintColor: navigationOptions.headerStyle.backgroundColor,
    }
  }
自定义titleView
  static navigationOptions = {    
    headerTitle: () => <#自定义view组件#>,
  };

设置导航栏按钮

  // headerRight为右侧导航栏按钮,注意赋值为函数时,右侧包裹组件的是圆括号。也可以不写函数直接提供组件。
  headerRight: () => (
    <Button 
      onPress = {navigation.getParam('increaseCount')}
      title = '+1'
      color = '#f00'
    />
  ),
  
  // 直接提供组件写法
  // headerRight: (
  //   <Button 
  //     onPress = {navigation.getParam('increaseCount')}
  //     title = '+1'
  //     color = '#f00'
  //   />
  // ),
自定义返回按钮

相关属性:

  • headerBackTitle: 返回按钮标题
  • headerTruncatedBackTitle: 返回按钮截断标题,默认为返回
  • headerBackImage: 返回按钮图片

可以在创建stackNavigator时指定默认的返回按钮样式,且在各个屏幕组件中,通过headerLeft属性对其进行重写,类似于headerRight

创建Modal页面组件

通过创建是stackNavigator,注册路由,并指定modemodal来实现modal跳转功能。调用modal页面,仍通过navigation.navigatepush调用即可。

const ModalStack = createStackNavigator(
  {
    Main: AppNavigator,		// AppNavigator是一个由stackNavigator包裹的页面合集
    MyModal: ModalScreen,
  },
  {
    mode: 'modal',
    headerMode: 'none',
  },
);

Tab导航

相关API:createBottomTabNavigator,使用方法类似于创建导航器。

const Tabs = createBottomTabNavigator(
  { ModalStack, AppNavigator2, AppNavigator3 },
  {
    initialRouteName: 'ModalStack',
  },
);
集中为tab导航控制器设置样式

例如:

defaultNavigationOptions: ({ navigation }) => ({
    tabBarIcon: ({ focused, horizontal, tintColor }) => {
      const { routeName } = navigation.state;
      let IconComponent = Ionicons;
      let iconName;
      if (routeName === 'Home') {
        iconName = `ios-information-circle${focused ? '' : '-outline'}`;
        // Sometimes we want to add badges to some icons.
        // You can check the implementation below.
        IconComponent = HomeIconWithBadge;
      } else if (routeName === 'Settings') {
        iconName = `ios-options`;
      }

      // You can return any component that you like here!
      return <IconComponent name={iconName} size={25} color={tintColor} />;
    },
  }),
  tabBarOptions: {
    activeTintColor: 'tomato',	// selectedColor
    inactiveTintColor: 'gray',	// normalColor
  },

注:

  • tabBarIconnavigationOptions的属性,但是在这里进行集中处理
  • tabBarIcon可以指定focused(是否选中)、tintColorhorizontal参数
添加角标

通过自定义Icon图标组件实现,在tab导航器的navigationOptions中返回对应的tabBarIcon即可。

Tab页面之间切换跳转

API类似于导航器的切换API: this.props.navigation.navigate