alibaba / hooks

A high-quality & reliable React Hooks library. https://ahooks.pages.dev/
https://ahooks.js.org/
MIT License
14.07k stars 2.71k forks source link

useClickAway判断不对 #1641

Closed cl1107 closed 6 months ago

cl1107 commented 2 years ago

复现链接:https://codesandbox.io/s/ahooks-clickaway-bug-v5t3zp?file=/src/App.js

这是使用react-use时的效果 https://user-images.githubusercontent.com/21985353/170660093-99cc3097-0ad9-45fa-866e-f8ab404aaf7a.mov

这是使用ahooks时的效果 https://user-images.githubusercontent.com/21985353/170660185-3abb028c-d082-4c59-8fda-46993ebbfd5a.mov

hchlq commented 2 years ago

感谢你的反馈,排查的情况是这样的:

  1. 我们默认是监听 document 上的 click 事件,因为阻止了冒泡,所以导致没有触发 clickAway 回调函数
  2. react-use 默认是监听 document 上的 mousedowntouchstart 事件,所以阻止冒泡也不会影响 clickAway 回调函数的触发
hchlq commented 2 years ago

我们可能需要讨论下是否要针对这一情况处理呢 @brickspert @crazylxr 我的想法是这样的:

  1. 因为我们需要监听顶层元素通过事件委托的方式看是否点击了目标元素之外,从而调用回调函数,在这一情况下阻止了冒泡,不触发回调感觉也有点合理
  2. 如果采用 mousedown 或者 touchstart 事件,我们就能兼容开发者在 click 下阻止冒泡,因为大多数情况下用 click 多于 mousedown 或者 touchstart 事件

各有一些优点和缺点,需要讨论看一下哈

@cl1107 欢迎提出你的想法💡

hchlq commented 2 years ago

上面的情况是在 react17 之后的,react17 之后,事件默认绑定在了根元素 #root 上,所以阻止冒泡直接不触发 document 上的 click 事件。

在 react17 以前,react 默认绑定事件在 document 上,即使阻止了冒泡也还是会触发 document 上的 click 事件。 在这个例子中,所以无论怎么点击 CodeSandbox文字 都不会显示 foobar。因为点击触发更新,重新渲染,visible 变为 true,此时调用 clickAway 回调函数,始终获取到最新的 visible ,即为 true,又将 visible 变为 false。

这个情况下我认为 ahooks 是合理的,因为 CodeSandbox文字 是在 foobar 之外的,也触发了 clickAway 事件,visible 变为 false 也是合情理的

可以这两个例子: react16.8.6 react18.0.0

cl1107 commented 2 years ago

最终我的建议是: 更改默认事件 clickmousedowntouchstart

我感觉改成mousedown和touchstart的效果更符合预期,点击第二个CodeSandbox文字,对于第一个CodeSandbox而言就是clickAway了,应该是要触发第一个的clickAway的回调函数的

hchlq commented 2 years ago

是的,不过默认如果改成了 mousedown 的话,有一个缺点就是在 mousedown 阻止冒泡的话和现在的 click 一样有问题的,只是说 mousedown 可能用的稍微少一点,所以默认事件建议改成 mousedown

14glwu commented 2 years ago

遇到类似问题,useClickAway 绑定的元素(假设为Parent)中的子元素(假设为Child1)如果销毁了,并且产生了其他的子元素(假设为Child2),点击Child2也会触发useClickAway。 导致现在只能修改监听事件或者阻止子元素冒泡

mrcaidev commented 2 years ago

遇到类似问题。

如果在 Modal 上使用 useClickAway 的话:

复现链接:useClickAway for modals

liuyib commented 6 months ago

楼主的使用方式,违背了 useClickAway 的设计用途:“监听目标元素外的点击事件”。

请考虑把不想触发 useClickAway 的元素放在 ref 引用的 dom 范围里(可以解决楼主在线 demo 里的问题):

+ <div ref={ref1} style={{ height: 100 }}>
      <div
        onClick={(e) => {
          e.stopPropagation();
          console.log("CodeSandbox");
          setVisible(true);
        }}
      >
        CodeSandbox
      </div>
-      <div ref={ref1} style={{ display: visible ? "block" : "none" }}>
+     <div style={{ display: visible ? "block" : "none" }}>
        foobar
      </div>
    </div>