问题描述:开发过程中发现
- 需要使用物理返回键,但是不起作用
- 每个界面都需要添加监听,代码冗余太严重
- 有的界面可以返回,有的界面无法返回
涉及第三方库:
ReactNavigation、 NativeBase。之所以添加这个标题,是因为有个问题与这个包有涉及
BackHandler简单用法
函数组件:两种不同的代码风格,个人比较喜欢第二种风格
// 方案 一
const HBPEventListener = () => {
console.log('ZhaoNanLog: 返回上一界面开始');
// TODO: 添加导航返回事件
console.log('ZhaoNanLog: 返回上一界面结束');
};
useEffect(() => {
console.log('ZhaoNanLog: 添加物理返回监听开始');
BackHandler.addEventListener('hardwareBackPress', HBPEventListener);
console.log('ZhaoNanLog: 添加物理返回监听结束');
return () => {
console.log('ZhaoNanLog: 移除物理返回监听开始');
BackHandler.removeEventListener('hardwareBackPress', HBPEventListener);
console.log('ZhaoNanLog: 移除物理返回监听结束');
};
}, []);
// 方案 二
useEffect(() => {
console.log('ZhaoNanLog: 添加物理返回监听开始');
const backListener = BackHandler.addEventListener('hardwareBackPress', () => {
console.log('ZhaoNanLog: 返回上一界面开始');
// TODO: 添加导航返回事件
console.log('ZhaoNanLog: 返回上一界面结束');
});
console.log('ZhaoNanLog: 添加物理返回监听结束');
return () => {
console.log('ZhaoNanLog: 移除物理返回监听开始');
backListener.remove();
console.log('ZhaoNanLog: 移除物理返回监听结束');
};
}, []);
类组件
HBPEventListener() {
console.log('ZhaoNanLog: 返回上一界面开始');
// TODO: 添加导航返回事件
console.log('ZhaoNanLog: 返回上一界面结束');
};
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.HBPEventListener);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.HBPEventListener);
}
解决代码冗余问题
本人使用方案二 建议使用方案一
因为每个界面都需要添加监听,卸载监听,所以有了大量重复的代码。重要的是一个应用有几十个界面,这样带来的工作量非常大。
方案一:这个问题,做了调查,有的人说,把代码添加到 App.js 入口文件中,这个解决方案还涉及到入口文件中没有导航的问题,需要写一个在没有导航的界面可以使用导航的工具文件。
参考 ReactNavigation 官网。有个标题叫 Navigating without the navigation prop 根据这篇文章写。
方案二:这是我自己琢磨出来的,目前没有发现什么问题。但是总觉得第一种方案更加专业高级的样子。
原理:物理返回事件 BackHandler事件的使用方案就是,在组件挂载后 添加事件监听,当组件将要卸载时移除监听。正是因为这种特殊的方式,所以才会导致代码冗余。
既然是这样,那么我们是否可以这样理解 在进入界面后,添加事件监听,在退出界面时移除监听
有什么可以对界面进入退出监听呢?那就是导航,果然,我在官网找到了导航的监听事件。
问题又回到了远点,我们可以这样进行,那么,导航是否提供一种全局的方法呢?
还真有, screenListeners 属性,可以将通过他的 props 传入每个界面
导航提供了四个事件:
- state 导航状态变化时触发
- focus 界面聚焦时触发
- blur 界面失去焦点时触发
- beforeRemove 界面将要离开时触发
到这里就很清晰了吧
将添加物理返回事件的监听添加到 state 或 focus 事件中
将移除物理返回事件的函数添加到 blur 或 beforeRemove 界面中
const HBPEventListener = () => {
console.log('ZhaoNanLog: 返回界面开始');
// TODO: 添加导航回退的代码
console.log('ZhaoNanLog: 返回界面结束');
};
return (
<Stack.Navigator
screenListeners={{
state: ({data}) => {
console.log('ZhaoNanLog: 添加物理返回监听');
BackHandler.addEventListener('hardwareBackPress', HBPEventListener);
},
beforeRemove: props => {
console.log('ZhaoNanLog: 移除物理返回监听');
BackHandler.removeEventListener(
'hardwareBackPress',
HBPEventListener,
);
},
}}>
.......
<Stack.Screen >
.......
</Stack.Navigator>
);
部分界面物理返回事件无响应
过程就不提了,直接说结论
讲过我大量测试和阅读源码,发现是 NativeBase 中提供的一个组件 与 物理返回事件的监听产生了冲突。我猜测应该是这个样子。
<Slide in={isLoading} placement="top"> ... </Slide>
这个组件与 BackHandler。 这两个东西肯定是有问题的,只要一起使用,就会出现问题。属性的其他选择我没有测试,至少现在提供的两个参数是会导致物理返回监听不起作用的。
解决方案:
1. 这个组件就是控制显示的方式的。所以在它需要显示的时候在渲染组件
{ isLoading && <Slide in={isLoading} placement="top"> ... </Slide> }
2. 使用它的其他属性 overlay=boolean, 这个属性是我阅读源码时发现的,官网文档上没有给,所以也不知道是干什么用的。字面意思就是覆盖的意思,所以
<Slide in={isLoading} placement="top" overlay={isLoading}> ... </Slide>
在阅读代码时,发现了一个很有意思的问题, 这个组件,应该是 在没有值也就是 undefined 时,应该与 false 走同一个判断,但很奇怪的是,它的效果与 overlay = true 的效果一样。所以我很奇怪。可能在某个地方有赋予默认值,也可能是我阅读的源码并不是该组件的源码。但是这个问题还是解决了,我更倾向于认为,这个在某个地方有赋予的默认值。
这个问题,也没有百度到,似乎,只有我一个人在使用这个组件时遇到了问题。也获取没有人同时使用了这个组件与物理返回事件。总之很开心的是我认真的把这个问题解决了。并找到了解决方案。
如果有人也遇到了这个问题,并且调查到了原因,麻烦告诉我一声。我很想知道是因为什么导致的这个现象。