超光速学习React Native笔记二:navigation导航器基础

官方文档:https://reactnavigation.org/zh-Hans/

和上一次一样,本人萌新一枚跟着官方文档一天学完导航器基础。

我们这次学习的是官方主推的导航库React Navigation,最新的3.X版本教程

保证一天学完!保证一天学完!保证一天学完!

安装

在项目中命令行窗口中,依次输入以下命令:

yarn add react-navigation
yarn add react-native-gesture-handler
react-native link react-native-gesture-handler

如果是安卓,还要在android/app/src/main/java/com/你的项目名/MainActivity.java上完成如下修改,+ 号表示要添加的内容:

 package com.reactnavigation.example;
 
 import com.facebook.react.ReactActivity;
 + import com.facebook.react.ReactActivityDelegate;
 + import com.facebook.react.ReactRootView;
 + import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
 
 public class MainActivity extends ReactActivity {
 
   @Override
   protected String getMainComponentName() {
     return "Example";
   }
 
 +  @Override
 +  protected ReactActivityDelegate createReactActivityDelegate() {
 +    return new ReactActivityDelegate(this, getMainComponentName()) {
 +      @Override
 +      protected ReactRootView createRootView() {
 +       return new RNGestureHandlerEnabledRootView(MainActivity.this);
 +      }
 +    };
 +  }
 }

开始学习

一、堆栈导航器

首先我们创建两个页面组件Home和Detail

 import React, { Component } from 'react';
 import { Text, View } from 'react-native';
 
 class HomeScreen extends Component {
   render() {
     return (
         <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
           <Text>Home</Text>
         </View>
     );
   }
 }
 
 class DetailScreen extends Component {
   render() {
     return (
         <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
           <Text>Detail</Text>
         </View>
     );
   }
 }
 
 export default class App extends Component {
 	render() {
 	    return <HomeScreen/>;
  	}
 }
1、导入标题栏导航器

注意:接下来的学习就自觉在头部导入用到的东西,不再作特殊说明

 import { createStackNavigator } from 'react-navigation';
2、创建一个标题栏导航器

createStackNavigator(RouteConfigs, StackNavigatorConfig)

生成顶部标题栏的函数

第一个参数 RouteConfigs 是设置路由,第二个参数 StackNavigatorConfig 是设置标题栏外观

在组件外部定义,创建一个标题栏导航器,我们先只使用参数一,参数二下面会讲到,如下所示:

 const MainStack = createStackNavigator(
   {	//路由名:组件名
     Home: HomeScreen,
     Detail: DetailScreen
   }
 );

如果路由名和组件名相同,还可以这样简写

 const MainStack = createStackNavigator( { HomeScreen, DetailScreen } )

参数一的完整写法:

 const MainStack = createStackNavigator(
   {	//路由名:组件名
     Home: {
     	screen: HomeScreen,		//组件名称
     	path: 'people/:name',	//当进行深度链接时,可以配置路径,从路径中提取操作和路由参数
     	//设置当主屏幕为Home页面组件时,标题栏的外观
     	navigationOptions: ({ navigation }) => ({	
     		title: 'Home'
     	})
     },
     Detail: DetailScreen
   }
 );

如上所示,我们现在只用 screen 属性,其它的下面会教

3、创建应用容器

因为上面的创建出来的 MainStack 导航器不是组件,无法让根组件渲染

所以你需要createAppContainer函数创建一个应用容器,把导航器包裹起来,再给根组件渲染

本质上就是将顶层的导航器链接到整个应用环境(不是人话)

你可以理解为把导航器变成组件,这样根组件就可以引用渲染了(这是人话)

 const AppContainer = createAppContainer(MainStack);

然后根组件就可以进行渲染

 export default class App extends Component {
     render() {
       return <AppContainer/>;
   }
 }

这已经是最后一步,此时可以看到你的App多了个顶部标题栏

为什么不直接导出AppContainer顶层导航组件,而是先让App根组件渲染再导出呢?

因为对应用程序根部的组件进行更多的控制通常更有用,所以导出一个只渲染了stack navigator组件的根组件(不是人话)

你可以这样理解,为了以后可以对顶层导航组件做更多操作(这是人话)

二、实现页面跳转

在HomeScreen组件中写一个跳转至详情页的按钮

 <Button title="去详情" onPress={() => this.props.navigation.navigate('Detail')}/>

现在我们可以从首页跳到详情页了

然后,我们在DetailScreen组件中,写三个按钮

 <Button title="再次去详情" onPress={() => this.props.navigation.push('Detail')}/>
 <Button title="回首页" onPress={() => this.props.navigation.navigate('Home')}/>
 <Button title="返回上一页" onPress={() => this.props.navigation.goBack()}/>

this.props.navigation.navigate()跳转至指定页面,不能是自身
this.props.navigation.push() 跳转至指定页面,可以是自身
this.props.navigation.goBack() 关闭当前页面并返回上一个页面

如果有已经加载的页面,navigate方法将跳转到已经加载的页面,而不会重新创建一个新的页面
push 总是会创建一个新的页面,所以一个页面可以被多次创建

三、带参跳转

1、发送参数

在跳转函数的第二参数位上,以对象形式传入你要传的参数

修改HomeScreen组件的按钮,如下所示

 <Button title="去详情" onPress={() => this.props.navigation.navigate('Detail',{
   itemId: 'abc',
   otherParam: '123'
 })}/>
2、接收参数

法一:getParam 函数必须设置参数一,要接收什么参数;参数二是可选参数,设置默认参数(如果没有参数传过来就使用默认参数)
法二:state.params 无法设置默认参数

在详情组件上添加以下代码

 <Text>{this.props.navigation.getParam('itemId', '111')}</Text>
 <Text>{this.props.navigation.state.params.otherParam}</Text>

这时,你的详情页面上就能显示你传的参数啦

你可以试试删除首页组件穿过来的itemId参数,这是详情组件就会显示默认参数 ‘111’

四、配置标题栏

1、设置标题

每个页面组件可以有一个名为**navigationOptions**的静态属性,我们在其中进行配置

在HomeScreen组件中,在 render 函数的上面,添加如下代码

static navigationOptions = {
 	title: 'Home'
}

这时,你的标题栏就有标题啦,苹果默居中,安卓默认左对齐

2、在标题中使用参数

为了让标题栏能使用参数(比如动态设置标题),我们需要把 navigationOptions 改造成函数,如下所示

static navigationOptions = ({ navigation}) => {
 return{
   title: 'Home'
 }
}

此时你就可以在标题栏里使用参数了,下面是动态调节标题栏的 title 例子

1、设置标题栏的 title ,默认叫 ‘Home’,接收名叫 ‘update’ 的参数

title: navigation.getParam('update', 'Home')

2、在该组件中添加一个按钮,并使用 setParams 函数设置参数

<Button title="修改标题" onPress={() => this.props.navigation.setParams({update: '修改'})}/>

现在,当你点击这个修改标题的按钮时,标题会由 Home 变成 ‘修改’,达成了动态设置标题的目的

3、调整标题样式

headerStyle:一个header的最外层的样式对象,如果你设置backgroundColor,他就是header的颜色。
headerTintColor:返回按钮和标题都使用这个属性作为它们的颜色。
headerTitleStyle:我们可以用它来完成Text样式属性。

4、设置共享的默认样式

如果我的每个组件的标题栏样式都相同,那我们可以设置共享的样式

还记得我们刚才说的导航器有两个参数吗?第一个是配置路由,第二个就是设置共享样式

在导航器的创建中进行如下设置

 const MainStack = createStackNavigator(
   {
     Home: HomeScreen,
     Detail: DetailScreen
   },
   {
     initialRouteName: "Home",
     defaultNavigationOptions: {
       headerStyle: {
         backgroundColor: 'green',
       },
       headerTintColor: 'red',
       headerTitleStyle: {
         fontWeight: 'bold',
       }
     }
   }
 );

initialRouteName:初始路由,如果不设置,就默认显示第一个路由 Home

defaultNavigationOptions:设置共享的标题栏默认样式,只要在这个路由中的组件都会生效

这样我们就可以在此处设置公共样式,然后在每个页面组件中,另外单独设置不同之处的样式。

注意:相同的样式,组件内定义的样式会覆盖导航器的样式

注意:这是骚操作例子,组件内设置标题栏外观时,还能调用标题栏导航器中的共享默认样式

在详情组件中,添加如下设置:

 static navigationOptions = ({ navigation, navigationOptions }) => {
   return{
     title: navigation.getParam('update', 'Home'),
     headerStyle: {		//这是骚操作例子,取默认导航器样式的相反颜色
       backgroundColor: navigationOptions.headerTintColor,
     },
     headerTintColor: navigationOptions.headerStyle.backgroundColor,
     headerTitleStyle: {
       fontWeight: 'bold'
     }
   }
 }

当组件内的设置标题栏的代码变得庞大起来时,我们可以用其它写法,在组件外定义,如下所示:

Home.navigationOptions = ({ navigation}) => ({ title: '123' })

还记得我们上面说的在创建导航器时,设置的路由信息吗?其中的navigationOptions用法一样:

 const MainStack = createStackNavigator(
   {
     Home: HomeScreen,
     Detail: {
     	screen: DetailScreen,
     	navigationOptions: ({ navigation }) => ({	
     		title: 'Detail'
     	})
     },
   }
 );

所以结合这两个例子,标题栏导航器里面的共享默认设置,也能在外面定义:

MainStack.defaultNavigationOptions = ({ navigation }) => ({	
  title: 'Detail1'
})
5、使用自定义组件替换标题

先自定义组件

 class LogoTitle extends Component {
   render() {
     return (
       <Image
         source={require('./spiro.png')}
         style={{ width: 30, height: 30 }}
       />
     );
   }
 }

然后在首页组件的 navigationOptions 中,添加并设置 headerTitle 属性

 static navigationOptions = {
   headerTitle: <LogoTitle />,
 };

这样就ok啦

headerTitle 是导航器的一个特殊属性,默认为一个 Text 组件,即能支持设置字符串标题,也能支持设置为组件标题

headerTitle 的属性会覆盖 title ,所以上面的标题样式对 headerTitle 也有效

五、标题栏按钮

1、向标题栏中添加一个按钮

在首页组件中的 navigationOptions ,添加并设置 headerRight,向标题栏右侧添加按钮

static navigationOptions = {
    headerTitle: <LogoTitle />,
    headerRight: (
      <Button onPress={() => alert('按钮')} title="Info" color="#fff" />
    )
};

当然你也可以像 headerTitle 一样,先在外部自定义组件,然后再赋值给 headerRight

2、标题栏和其所属的页面的交互

navigationOptions 中this绑定的不是HomeScreen实例,所以你不能调用setState方法和其上的任何实例方法。

所以先看下面的例子:

 class HomeScreen extends React.Component {
   static navigationOptions = ({ navigation }) => {
     return {
       headerTitle: <LogoTitle />,
       headerRight: (
         <Button	//步骤一:在标题栏中,使用 getParam 接收参数 ‘count’
           onPress={navigation.getParam('count')}
           title="+1"
           color="#fff"
         />
       ),
     };
   };
   // 步骤二:为该参数赋值为一个函数 increaseCount()
   componentDidMount() {
     this.props.navigation.setParams({ count: this.increaseCount });
   }
   
   state = { count: 0 };
   // 步骤三:定义该函数,比如这里调用了 setState 函数
   increaseCount = () => {
     this.setState({ count: this.state.count + 1 });
   };
 }

注意:由于count 参数在 componentDidMount 中设置,如果页面组件没有被加载出来,标题栏的按钮就不会有任何反应。要解决此问题,可以使用导航器提供的生命周期事件,下面第七节有说

你也可以使用状态管理库ReduxMobX来进行标题栏和页面之间的通信,就像两个不同组件之间的通信一样

3、自定义返回按钮

headerBackImage:自定义返回按钮的图片,默认是左箭头

headerBackTitle:iOS中自定义返回按钮旁的文字,默认null

headerLeft:和 headerRight 用法一样,你可以自定义并覆盖左侧默认按钮

标题栏可配置选项非常丰富,可以在看详情:https://reactnavigation.org/docs/zh-Hans/stack-navigator.html

4、为共享的默认样式添加交互

通过刚才的学习,我们在导航器的默认样式中,也添加了一个左侧按钮:

 const MainStack = createStackNavigator(
   {
     Home: HomeScreen,
     Detail: DetailScreen
   },
   {
     initialRouteName: "Home",
     defaultNavigationOptions: {
       headerLeft: (
 	     <Button onPress={() => alert('按钮')} title="返回"/>
 	   )
     }
   }
 );

如果我们要按这个按钮,然后要返回上一页,那该怎么做呢?

和上面组件内定义的标题栏样式一样,要改造成函数,如下所示:

defaultNavigationOptions: ({navigation}) => ({
 	headerLeft: (
 		<Button onPress={() => navigation.goBack()} title="返回"/>
 	)
})

但这样写的话,首页也有返回按钮了,所以要加以判断:

defaultNavigationOptions: ({navigation}) => ({
 	headerLeft: () => {
     const {routeName} = navigation.state
     if(routeName == 'Home'){
       return null
     }
     return (
       <Button onPress={() => navigation.goBack()} title="滚"/>
     )
   }
})

如上所示,使用 navigation.state 可以获取当前路由的名称,然后再进行判断

坑:headerRight 不行!

六、全屏模态(Modal)和嵌套导航器

比如你在清空购物车时,系统会弹出来问你是否确定,这时候你只能跟弹窗交互,点击弹窗以外区域没有任何反应,这就是模态。

第一,创建打开模态时要展示的组件

 class ModalScreen extends React.Component {
   render() {
     return (
       <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
         <Text>This is a modal!</Text>
         <Button onPress={() => this.props.navigation.goBack()} title="关闭模态"/>
       </View>
     );
   }
 }

第二,创建模态的打开方式,这里是在HomeScreen组件中写一个按钮:

 <Button title="打开模态" onPress={() => this.props.navigation.navigate('Modal')}/>

还记得下面这是我们之前设置的导航器 MainStack 吗?

 const MainStack = createStackNavigator(
   {
     Home: HomeScreen,
     Detail: DetailScreen
   },
   {
     initialRouteName: "Home",
     defaultNavigationOptions: {
       headerStyle: {
         backgroundColor: 'lightblue',
       },
       headerTintColor: 'yellow',
       headerTitleStyle: {
         fontWeight: 'bold',
       }
     }
   }
 );

第三,现在把 MainStack 和 模态合起来 ,再创建一个新的导航器

 const RootStack = createStackNavigator(
   {
     Main: MainStack,
     Modal: ModalScreen
   },
   {
     mode: 'modal',
     headerMode: 'none'
   }
 );

第四,修改应用容器使用的导航器,把导航器 MainStack 改为合起来后的新的导航器 RootStack

const AppContainer = createAppContainer(RootStack);

大功告成,现在快点击首页组件中的打开模态按钮试试!

使用navigation的模态,就必须使用嵌套导航器,为什么?

看看第三步创建的新的导航器和之前有什么不同之处吗?第二个参数对象里的属性设置不一样了!

1、标题栏的设置都没有了,这意味着当你打开模态时,标题栏没有了,达成了我们要的全屏模态的效果

2、第二个参数对象里可以设置模态的样式属性啦
mode:模态打开效果,默认值是 card,还可以设置为 modal(从页面底部划入),但只要iOS有效
headerMode:页眉,默认值是null(没有页眉);设置为 float,就会渲染保持顶部的页眉标题,iOS常见模式;设置为 screen,页眉和页面一起淡入淡出,安卓常见模式

更多属性:https://reactnavigation.org/docs/en/stack-navigator.html#stacknavigatorconfig

上面的导航器嵌套,你可以如下图所示理解:
在这里插入图片描述
除了模态,导航器的嵌套还有别的作用,等到后面学到底部选项卡时,每个选项页都会有自己的导航器

注意:嵌套导航器是指用比如 createStackNavigator 函数创建的导航器
千万不要用 createAppContainer 函数创建多个应用容器

七、导航生命周期

1、React生命周期函数的变化

当我们使用导航器的路由进行跳转时,react的生命周期变得更复杂

假设有一个导航器,含有路由A和路由B,默认初始加载路由A页面

当初始化加载A时,A的 componentDidMount 会被调用

当从A跳到B时,B的 componentDidMount 一样会被调用,

但此时A依然在堆栈中保持被加载状态,所以A的 componentWillUnMount 不会被调用

然后从B跳回到A时,B的 componentWillUnmount 会被正常调用,A的 componentDidMount 不会被调用,因为A一直都是加载状态

解释:这是符合我们的日常理解,当我们使用App时,如果一直跳转页面,会越来越卡,因为上一页并没有关闭,页数越多,越耗费手机性能,所以一般推荐顶多写五层页面。如果有更多的层数推荐手动关闭之前的页面。

然后当你一直按返回上一页时,你的手机也会逐渐变得流畅,因为这些页面都关闭了

简而言之:上面的例子,因为初始化是A页面,所以A是父层页面,B是子层页面,所以A到B,A不会被关闭,B到A,B会被关闭。

2、React Navigation生命周期事件

针对上述情况,导航器为我们提供了4个生命周期事件来获取页面状态。

willFocus - 页面将获取焦点
didFocus - 页面已获取到焦点(如果有过渡动画,等过渡动画执行完成后响应)
willBlur - 页面将失去焦点
didBlur - 页面已获取到焦点(如果有过渡动画,等过渡动画执行完成后响应)

法一:使用 NavigationEvents 组件来订阅上面4个事件

import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';

const MyScreen = () => (
<View>
 <NavigationEvents
   onWillFocus={payload => console.log('will focus',payload)}
   onDidFocus={payload => console.log('did focus',payload)}
   onWillBlur={payload => console.log('will blur',payload)}
   onDidBlur={payload => console.log('did blur',payload)}
 />
 {/* 你的视图层代码 */}
</View>
);

export default MyScreen;

console.log换成你想要在这个事件执行的函数就行。payload是自带参数,可以不使用,写成 ( ) => { … }

法二:使用 withNavigationFocus 高阶组件

它可以将 isFocused 这个 prop 传递到一个被包装的组件,看下面例子:

import React from 'react';
import { Text } from 'react-native';
import { withNavigationFocus } from 'react-navigation';

class Home extends React.Component {
  render() {
    return <Text>{this.props.isFocused ? 'Focused' : 'Not focused'}</Text>;
  }
}

export default withNavigationFocus(Home );

因为最下面那行代码中,withNavigationFocus 把 Home 这个组件包裹起来
所以 Home 中可以通过 this.props.isFocused 获取到当前是否处于聚焦状态
然后三元表达式,如果true,渲染 ‘‘Focused’’,反之,渲染 ‘Not focused’

如果你需要在页面组件的渲染方法中使用焦点状态,这个组件很有用。

你也可以不使用高阶组件,直接如下使用:

let isFocused = this.props.navigation.isFocused();

法三:使用 addListener 函数订阅生命周期事件

参数一是要监听的事件,参数二是监听的事件触发时要执行的函数

和法一一样,参数二的函数自带 payload 参数,可以不用,里面定义你想要执行的内容

const abc = this.props.navigation.addListener(
  'didBlur',
  payload => {
  	console.debug('didBlur', payload);
  }
);

当你执行完内容时,你还可以移除监听:

abc.remove()

恭喜你,已经学完Navigation的基础,请在评论里面告诉我,有没有骗你,一天内就可以学完。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值