您的位置:首页 > Web前端 > React

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

2019-03-16 00:55 525 查看

官方文档: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的基础,请在评论里面告诉我,有没有骗你,一天内就可以学完。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: