超光速学习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 中设置,如果页面组件没有被加载出来,标题栏的按钮就不会有任何反应。要解决此问题,可以使用导航器提供的生命周期事件,下面第七节有说
你也可以使用状态管理库Redux和MobX来进行标题栏和页面之间的通信,就像两个不同组件之间的通信一样
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的基础,请在评论里面告诉我,有没有骗你,一天内就可以学完。
- 学习ReactNative笔记整理一___JavaScript基础
- 学习React Native 笔记(一)React基础知识
- react native 开发基础=javaScript=学习笔记----函数
- React-Native学习笔记之:导航器Navigator实现页面间跳转
- React Native 学习笔记-iOS(一)
- React Native 学习笔记(一)
- React-Native学习笔记——技术栈及简介
- react-native 学习笔记
- 学习ReactNative笔记三 __React基础
- react学习笔记1--基础知识
- React Native 学习笔记二(React Native开发环境的搭建for mac)
- React Native 学习笔记十四(原生模块之数据回调)
- React Native 学习笔记十九(关于开发环境)
- React Native入门学习笔记三(JSX语法)
- React-Native学习笔记之:Modal实现覆盖效果(类似安卓中PopuWindow)
- React-native 学习笔记(三)
- React 基础 学习笔记
- react-native 学习笔记
- react native 学习笔记之hello world
- 学习ReactNative笔记二 __ECMAScript新功能