目录
前言
react-navigation虽然一直在用,但始终没有将官网(英文版)给过一遍,最近要搭建一个新项目,对于导航嵌套有一定的要求,就借此机会将文档详细过了一下。阅读过程中发现很多想要的功能人家本身就支持,因为之前没有认真看文档,自己又造了一遍轮子而且还留下一些坑。借此机会将所有文档给梳理一下
1. 名词解释
- navigator
导航器,比如:StackNavigator/TabNavigator/DrawerNavigator - screen
在导航器中配置的真正显示在屏幕上的组件,只有这类组件的props上才有navigation对象 - navigation
导航对象,我们通过他进行屏幕的跳转 - route
路由对象,包含着该页面的key、name及路由参数 - state
路由栈
2. 各种导航API
主要分如下几类:
-
DrawerActionHelpers
-
TabActionHelpers
-
StackActionHelpers
-
NavigationHelpersCommon
-
EventConsumer
-
NavigationProp
下面说几个我感觉需要需要记录的
-
navigate、push
navigation.navigate('RouteName')
,如果指定路由在路由栈中存在,会回到该页面,否则会push
到一个新页面;navigation.push('RouteName')
,无论指定路由是否在路由栈中,都会新建一个路由页面并跳转过去。疑问: A
push
B,Bpush
C,Cpush
D,Dnavigate
A后,路由栈中还会存在BCD么不会存在,经测试,
navigate
到哪个已存在的页面,该页面之后的路由都会被清空。下面附上两个case的结果
Dnavigate
A后,路由栈中只剩A;Dnavigate
B后,路由栈中只剩A、B -
setParams
上个页面传过来的路由参数要做修改,直接通过setParams更改即可,不用再次新建一个state处理疑问:setParams会触发render么
会,我将parmas的属性做为文本内容,点击按钮setParams后,文本内容更新
-
setOptions
可以使用该API直接更新标题,另外当导航想要配置自定义的button组件时,也可以通过setOptions配置 -
getParent
获取父导航,比如A页面是直接嵌套在Stack导航下,Stack是嵌套在Drawer下,在A页面是无法直接使用navigation.openDrawer()
打开导航,需要先获取父导航navigation.getParent().openDrawer()
-
dispatch
导航操作可以通过dispatch分发对应的action实现,比如:navigation.dispatch(DrawerActions.openDrawer)
3. 生命周期
-
useFocusEffect
页面显示时调用,传递的回调应该包裹在React.useCallback
中,以避免过于频繁地调用import { useFocusEffect } from '@react-navigation/native'; function Profile() { useFocusEffect( React.useCallback(() => { // Do something when the screen is focused return () => { // Do something when the screen is unfocused // Useful for cleanup functions }; }, []) ); return <ProfileContent />; }
-
useIsFocused,当前页面是否处于焦点状态
如果您使用的是选项卡或抽屉式导航器,因为导航器中的所有屏幕可能会同时渲染并保持渲染 - 这意味着StatusBar您设置的最后一个配置将被使用(可能在最后一个选项卡上您的标签导航器,而不是用户所看到的。为了解决这个问题,我们必须让状态栏组件知道屏幕焦点,并仅在屏幕获得焦点时渲染它。我们可以通过使用useIsFocused钩子并创建一个包装器组件来实现这一点:import * as React from 'react'; import { StatusBar } from 'react-native'; import { useIsFocused } from '@react-navigation/native'; function FocusAwareStatusBar(props) { const isFocused = useIsFocused(); return isFocused ? <StatusBar {...props} /> : null; }
-
疑问:在该页面的子组件是否可以使用这两个API
可以,将这两个API放到子组件里,可以得到正确的结果 -
疑问:有useBlurEffet么
找了,没有,如果想要使用,使用使用如下订阅的方式const unsubscribe = navigation.addListener('blur', () => { // Screen was focused // Do something });
另外官方提供的页面生命周期只有
focus
和blur
4. 身份验证
这里介绍的这种方案更适用于没有访客模式的那种App,在此我学到了两个小知识点:
- 路由注册是可以动态切换的,说不定以后碰到啥奇怪的业务场景就用上了
- 可以将一些配置相同的路由放到一个路由
group
中,在group
上统一配置
通过这种案例,我了解到
5. 安全区域
- 内部使用的是
react-native-safe-area-context
来处理的安全区域,其他的方式要么有问题,要么已经过时 - 可以通过
SafeAreaView
的edges
属性控制哪边自动填充被盖住的区域,比如:当前页面没有导航,我又不想让安全区域顶到到屏幕的最上方,就可以将该属性设置为['top']
, - 使用useSafeAreaInsets钩子能够得到安全区域的偏移量,然后做你想做的事情
6. 状态栏
之所以要判断当前页面是否是焦点,是因为若项目中有选项卡或抽屉,可能会同时渲染,造成一些莫名的异常
import * as React from 'react';
import { StatusBar } from 'react-native';
import { useIsFocused } from '@react-navigation/native';
function FocusAwareStatusBar(props) {
const isFocused = useIsFocused();
return isFocused ? <StatusBar {...props} /> : null;
}
7. 多个抽屉
在抽屉路由中,控制抽屉方向的drawerPosition
属性不可动态更改,因此若项目中既有左抽屉又有右抽屉,就需要嵌套两个抽屉实现,在这里也用到了一个getParent
的API来获取两个抽屉路由的导航实例进而控制在哪边显示抽屉
8. 嵌套导航的options该如何配置
首先摆上官方加粗的一句话:
不管是单个导航还是嵌套导航,修改父导航的
options
只能在该导航下的screen component
中通过navigation.setOptions
进行修改,
我理解的是:A嵌套B,B嵌套C,无法在C中修改A的options
9. 返回上一级时的二次确认
可以用两种方式来实现:
- BackHandler监听
- beforeRemove事件,其中提到的
preventDefault
在iOS上无效
在没有beforeRemove
之前,要实现这种效果基本就是如下几种方式:
- 重新
header
的返回按钮 - 禁用滑动返回手势
- 重写Android的系统返回事件
这个新增的API除了代码量更少外,还有以下几个特色:
- 他不跟任何的特定按钮绑定,自定义的返回按钮也可触发
- 他不跟任何的特定action绑定,任何从state中移除route的操作都可触发,比如:pop/reset/swipe 等
- 支持嵌套导航,比如:由父路由发送的action导致的移除route也会触发该监听
- 用户仍然可以滑动返回,但如果监听事件里做了
prevented
处理,该侧滑操作将会被取消 - 在监听事件里可以通过
navigation.dispatch(e.data.action)
继续最初确定的路由回退操作
虽然很强大,但也有一些限制:
-
当用户由于不受导航状态控制的操作而退出屏幕时,不会触发该监听,比如:
- 退出退出App、退出浏览器
- 由于预期或非预期的原因,造成screen被卸载
-
在
@react-navigation/native-stack
中,如下两个操作该监听会失效:- 侧滑返回
- 点击自带的返回按钮
解决方案也简单,对于前者只能禁用侧滑返回,对于后者自定义返回按钮就行
10. 获取到navigation
实例的三种方式
- 从
props
中取 - 从
useNavigation
中取(本质上是通过context获取到了screen component上的navigation) - 获取
createNavigationContainerRef
前两者都是在组件内获取在组件内使用,但当我要在非组件内实现路由跳转(比如:redux中),就要使用第三种方式,这种方式跟前两者相比有如下缺陷:
4. 针对screen component的一些特定API,ref不支持,比如:setOption
/push
…
5. 路由跳转的灵敏度和用户体验有所降低
因此对于第三种方式,应该尽可能的避免使用,除非迫不得已
11. Deep linking与react-navigation的融合
Linking的主要作用是通过访问指定连接来启动App,在项目中,我们可以获取链接上的参数,通过参数来判定跳转到哪个页面。导航库官方推荐使用官方提供的融合方案,强烈不建议自行手动将两者结合,主要是因为官方的方案针对一些边缘case都做了处理,若自行手动处理会比较麻烦。官方也详细介绍了基础配置及各种项目下如何测试,具体看deep-linking
基础介绍和基础配置搞完了,官方也专门提供了一个文档说明里面的各种case如何配置,详细看configuring-links,我下面讲我认为比较重点的内容标记一下:
- 在
config
中配置路由和path的映射关系时,嵌套关系要和实际路由嵌套一致 - 配置path的方式有多种,不同场景对应的
path
有些差异,大家根据实际需求做选择 - 在
config
中,可通过parse
对参数进行自定义处理,还提到一个stringify
参数,没有搞明白他是要干嘛,经测试他也没有执行 - 若在
config
中不配置initialRouteName
,通过Link冷启动App,无法返回上一级,另外在此场景下,url中无法给初始路由设置参数,返回的页面必须无参数据,或者通过screen
的initialParams
给设置初始参数 - 官方本质上是将url转化成state,然后使用dispatch做分发实现的路由跳转,为了为了增强扩展性,官方将
getStateFromPath
和getPathFromState
开放给开发者,让开发者能够做更高阶的事情
在web上的一些注意点::
- 配置link前在web上的跳转不会改变url,配置link后会将路径和参数拼到url后
- 路径默认是按照路由嵌套的顺序展示(若嵌套过深,路径过长,官方提供有简化方案)
- 参数默认是按照query的格式拼接,若在path中有指定拼接方式,跳转时会按照指定格式处理
12. 对web的支持
对web的支持还是比较友好的,无论从文档上还是从实际使用上,都还没有发现有啥需要特别处理的。
13. 路由跳转监听
可以在这里做些页面的埋点操作
14. 主题切换及自定义
还挺完善的
15. state持久化
我们在调试深层级页面时,有时一旦reload,还得再次一层一层点进去,这里做的事情就是实时将路由栈在本地维护一份,reload后从本地取出,恢复路由栈
16. ts类型
本质上主要解决两个问题:
- 路径和对应的参数提示
- 各种navigator上的API提示
官方针对导航嵌套的场景也帮助我们做了示例,若这里配置齐全,在配置Linking时会比较方便
17. 其他的一些API
- getFocusedRouteNameFromRoute
获取到某个嵌套导航器下获取到焦点的路由名称,使用场景:要求tab的标题跟其下页面的路由名称有关
18. 导航嵌套
上诉都看完后,或多或少对导航嵌套这一块应该都不陌生了。接下来我提供几种嵌套导航Demo,并列出相关特性,大家可根据业务情况做抉择,或者扩展出更适合自己业务的导航嵌套模式。
不知不觉,本篇幅已快七千,又因为这块整体比较独立,我就单开一篇文章,react-navigation的各种导航嵌套优劣势
19. API介绍
在上面的介绍中,已经接触了一些API,但始终缺乏对这些API的系统化认识,我再过一遍的目的,主要想让自己做下查漏补缺,该内容整体比较独立,也单开一个文章,react-navigation的API梳理
20. 自定义导航
官方原文
我之所以提到,是想说明该库支持自定义导航,用到的时候再详细研究吧
21. 可替换的导航库
- react-native-router-flux: 基于react-navigation,但提供了一些不同的API
- react-native-navigation:在iOS和Android上使用底层本地API,类似于createNativeStackNavigator。这是React Navigation的一个流行替代方案,如果您试图将React Native集成到现有的大型本地应用程序中,它可能更适合您。