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);
};

然後我們看一下效果

在這裏插入圖片描述

可以看到 onPress 確實觸發了兩次,且第一次在 touchend 之後,第二次在 mouseup 之後,所以我們可以合理推測 React Native Web 的 Touchable 組件將 onPress 應該就是在 touchendmouseup 都綁定上了

那爲何只有特定安卓 h5 會有這個問題呢?照上面的推論,那應該每個設備都有這個問題

我們可以換至不會觸發問題的瀏覽器下看一下效果(在我的 onePlus 6T 下只有微信環境穩定復現,其他瀏覽器都不會有問題)

在這裏插入圖片描述

似乎差不多不過就是沒有觸發兩次 onPress,讓我們再來看一下源碼的實現

在這裏插入圖片描述

簡而言之就是,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 同時存在的時候

在這裏插入圖片描述

從上面的文章可以知道,我們可以在 touchend handler 中執行 event.preventDefault() 去取消 mouse event 的發送

也就是說,我們可以在有問題的 onPress 里加上 event.preventDefault() 就可以了(上面文章打不開的,網上還有其他很多優質博客,這裏不再贅述)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章