如何在 React 应用程序中的所有 onclick 处理程序之前 运行 公共代码? (解决 Safari 中键点击错误)

How to run common code before all onclick handlers in a React app? (to work around Safari middle-button click bug)

如何在 React 应用程序中的所有 onclick 处理程序之前 运行 编写代码,而不必向每个处理程序添加代码?具体来说,我想在全局范围内确保所有 React onclick 处理程序都忽略中键点击。目标是根据 W3C 标准解决 12-year-old WebKit bug where Safari emits a click event when the middle mouse button is pressed, instead of the auxclick event that's mandated 问题,该标准由 Chrome 和 Firefox 发布。

因为一些用户在使用鼠标滚轮滚动时不小心触发了中间按钮点击,我想全局忽略这些意外点击。怎么样?

我要注入的代码很简单:

if (e.button !== 0) {
  e.stopPropagation();
}

但我不确定将它注入到哪里,以便它 运行 在我的应用程序中的所有事件处理程序之前。

一个潜在的并发症是我不想完全忽略中间点击(因为浏览器有一个默认行为,中间点击 <a> 元素将在新选项卡中打开 link ).相反,我只是想阻止 React 对那些无效的 click 事件做任何事情。

为了解决这个问题,我想我必须做一些棘手的事情,比如猴子修补 React,但事实证明,一个不棘手的解决方案是可能的:只需将整个应用程序包装在一个顶级组件中,该组件捕获单击事件使用 onClickCapture 事件而不是正常的 click 事件。这是我为此目的编写的一个简单组件。

IgnoreSafariMiddleClicks.tsx

import React, { useCallback, MouseEventHandler, ReactNode } from 'react';
export default function IgnoreSafariMiddleClicks({ children }: { children: ReactNode }) {
  const onClick = useCallback<MouseEventHandler>(e => {
    if (e.button !== 0) {
      // Prevent middle clicks from being handled by click handlers on Safari
      // browsers, in order to work around this 12-year-old WebKit bug:
      // https://bugs.webkit.org/show_bug.cgi?id=22382
      e.stopPropagation();
    }
  }, []);
  return <div onClickCapture={onClick}>{children}</div>;
}

如果您不使用 TypeScript,这里是组件的纯 JS 版本:

IgnoreSafariMiddleClicks.js

import React from 'react';
export default function IgnoreSafariMiddleClicks({ children }) {
  const onClick = useCallback(e => {
    if (e.button !== 0) {
      // Prevent middle clicks from being handled by click handlers on Safari
      // browsers, in order to work around this 12-year-old WebKit bug:
      // https://bugs.webkit.org/show_bug.cgi?id=22382
      e.stopPropagation();
    }
  }, []);
  return <div onClickCapture={onClick}>{children}</div>;
}

用法

import React from 'react';
import IgnoreSafariMiddleClicks from './IgnoreSafariMiddleClicks';
export default function App() {
  return (
    <IgnoreSafariMiddleClicks>
      <div>
        <button onClick={() => console.log('Left clicked!')}>
          click me!
        </button>
      </div>
    </IgnoreSafariMiddleClicks>
  );
}

我发现的一个问题是 SyntheticEvent.nativeEvent.stopImmediatePropagation doesn't work in this scenario, because other React event handlers continue to be called afterwards. I had to use the stopPropagation method of SyntheticEvent

我花了一段时间才想出这个解决方案(尤其是捕获阶段的技巧和 stopPropagationstopImmediatePropagation 的问题),我没有看到这个中间按钮- 在网上其他任何地方吞下解决方案,因此将其张贴在这里以帮助下一个寻找解决方案的人。

另一种解决方案可能是添加一个 polyfill,用符合标准的 auxclick 事件替换 Safari 的不良 click 事件,但是 Google didn't return anything promising 并编写一个事件polyfill 超出了我对 React 事件处理的有限了解,所以我选择了上面的 wrapper-component 解决方案。