官方文档: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';
1
2、创建一个标题栏导航器
createStackNavigator(RouteConfigs, StackNavigatorConfig)
生成顶部标题栏的函数
第一个参数 RouteConfigs 是设置路由,第二个参数 StackNavigatorConfig 是设置标题栏外观
在组件外部定义,创建一个标题栏导航器,我们先只使用参数一,参数二下面会讲到,如下所示:
const MainStack = createStackNavigator(
{ //路由名:组件名
Home: HomeScreen,
Detail: DetailScreen
}
);
如果路由名和组件名相同,还可以这样简写
const MainStack = createStackNavigator( { HomeScreen, DetailScreen } )
1
参数一的完整写法:
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')}/>
1
现在我们可以从首页跳到详情页了
然后,我们在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')
1
2、在该组件中添加一个按钮,并使用 setParams 函数设置参数
<Button title="修改标题" onPress={() => this.props.navigation.setParams({update: '修改'})}/>
1
现在,当你点击这个修改标题的按钮时,标题会由 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' })
1
还记得我们上面说的在创建导航器时,设置的路由信息吗?其中的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')}/>
1
还记得下面这是我们之前设置的导航器 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);
1
大功告成,现在快点击首页组件中的打开模态按钮试试!
使用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();
1
法三:使用 addListener 函数订阅生命周期事件
参数一是要监听的事件,参数二是监听的事件触发时要执行的函数
和法一一样,参数二的函数自带 payload 参数,可以不用,里面定义你想要执行的内容
const abc = this.props.navigation.addListener(
'didBlur',
payload => {
console.debug('didBlur', payload);
}
);
当你执行完内容时,你还可以移除监听:
abc.remove()
1
恭喜你,已经学完Navigation的基础,请在评论里面告诉我,有没有骗你,一天内就可以学完。
---------------------
作者:前端大法好
来源:CSDN
原文:https://blog.csdn.net/weixin_43943881/article/details/88561446
版权声明:本文为博主原创文章,转载请附上博文链接!