React Native Web 安卓h5 Touchable onPress 触发两次问题解决

在维护基于 React Native Web 的 h5 项目时,遇到这样的一个 bug,在部分安卓 h5 页面一些点击事件会稳定触发两边,这样的问题在 iOS 和 pc 调试 h5 都是好的,甚至同一机型只要另外换一个浏览器可能也会有不同的效果。

在调试阶段,发现只要把 TouchableHighlight 等组件都干掉,换成 div,点击事件也从 onPress 改到 onClick,就全部是好的,不过由于是三端项目,需要 TouchableHighlight 这些 React Native 组件,当然不能这么改了了事,不过也给我定位到可能是 React Native Web Touchable 类组件的 bug

定位到是这个问题,很快可以找到对应问题 issue 以及 RNW 作者的回复和解决 链接

管中窥豹

根据 issue 里大家的讨论结合后面作者 necolas 的修复改动,我们可以加一些 console 来看下引起问题的原因

首先在有问题的 onPress 函数里添加 console.log('触发 onPress 函数')

然后在 node_modules/react-native-web/dist/modules/ResponderEventPlugin/index.js 找到以下的代码(在备注的地方加入console)

ResponderEventPlugin.extractEvents = function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
  var hasActiveTouches = ResponderTouchHistoryStore.touchHistory.numberActiveTouches > 0;
  var eventType = nativeEvent.type;
  // 在这加 console
  console.log('responder >>> ', topLevelType, Date.now() - lastActiveTouchTimestamp)
  var shouldSkipMouseAfterTouch = false;
  if (eventType.indexOf('touch') > -1) {
    lastActiveTouchTimestamp = Date.now();
  } else if (lastActiveTouchTimestamp && eventType.indexOf('mouse') > -1) {
    var now = Date.now();
    shouldSkipMouseAfterTouch = now - lastActiveTouchTimestamp < 250;
  }

  if (
  // Filter out mousemove and mouseup events when a touch hasn't started yet
  (eventType === 'mousemove' || eventType === 'mouseup') && !hasActiveTouches ||
  // Filter out events from wheel/middle and right click.
  nativeEvent.button === 1 || nativeEvent.button === 2 ||
  // Filter out mouse events that browsers dispatch immediately after touch events end
  // Prevents the REP from calling handlers twice for touch interactions.
  // See #802 and #932.
  shouldSkipMouseAfterTouch) {
    return;
  }

  var normalizedEvent = normalizeNativeEvent(nativeEvent);

  return originalExtractEvents.call(ResponderEventPlugin, topLevelType, targetInst, normalizedEvent, nativeEventTarget);
};

然后我们看一下效果

React Native Web 安卓h5 Touchable onPress 触发两次问题解决_第1张图片

可以看到 onPress 确实触发了两次,且第一次在 touchend 之后,第二次在 mouseup 之后,所以我们可以合理推测 React Native Web 的 Touchable 组件将 onPress 应该就是在 touchendmouseup 都绑定上了

那为何只有特定安卓 h5 会有这个问题呢?照上面的推论,那应该每个设备都有这个问题

我们可以换至不会触发问题的浏览器下看一下效果(在我的 onePlus 6T 下只有微信环境稳定复现,其他浏览器都不会有问题)

React Native Web 安卓h5 Touchable onPress 触发两次问题解决_第2张图片

似乎差不多不过就是没有触发两次 onPress,让我们再来看一下源码的实现

React Native Web 安卓h5 Touchable onPress 触发两次问题解决_第3张图片

简而言之就是,now - lastActiveTouchstamp < 250 时,就可以使 shouldSkipMouseAfterTouch = true,从而跳过绑定在 mouse 上的 onPress 事件,从而避免触发两次的情况

而我们在前面 console 后面加上的数字就是 now - lastActiveTouchstamp 的值,可以看到问题浏览器下,该值均在 320+,不在 250ms 的安全阈值内,无法跳过绑定在 mouse 上的 onPress 事件,因此触发两次 onPress。

解决方案

找到引起问题的原因,就可以需要找到一些解决方法了,下面我抛砖引玉,提供三种解决方案的思路

1. 升级 react-native-web 版本

在 0.11.7+ 的版本,RNW 作者将安全阈值更新到了 1000ms,这就可以解决绝大多数的浏览器问题。(当然了在公司某台巨卡测试机上 仍有问题,根据上面的调试,测出来的相距时间达到了 1200ms+)

commit 信息

2. 利用 postinstall 钩子函数处理

不过目前项目已经很难做依赖库升级的情况下,我们可以利用 npm 的 postinstall 钩子函数,写一些脚本在 npm i 后执行,人为修改本地 node_modules 里的文件

举个例子,可以利用 sed 命令来处理

sed -i '' "s/250/1500/g" ./node_modules/react-native-web/dist/modules/ResponderEventPlugin/index.js

这种方式很灵活,可以灵活改动安全阈值,比如我就把 250 改到了 1500

3. 使用 preventDefault 函数

移動端瀏覽器 :當 Touch Event 與 Mouse Event 同時存在的時候

React Native Web 安卓h5 Touchable onPress 触发两次问题解决_第4张图片

从上面的文章可以知道,我们可以在 touchend handler 中执行 event.preventDefault() 去取消 mouse event 的发送

也就是说,我们可以在有问题的 onPress 里加上 event.preventDefault() 就可以了(上面文章打不开的,网上还有其他很多优质博客,这里不再赘述)

你可能感兴趣的:(React,Native)