从 Dropdown 的 React 实现中学习到的

本文探讨了在React中实现Dropdown组件时遇到的事件处理问题,特别是如何处理点击菜单外部收起菜单的情况。作者指出,相比于使用React的合成事件,直接使用原生的`addEventListener`更为简便。然而,React的`stopPropagation`无法阻止原生事件冒泡到document,为此提出了两种解决方案,并讨论了`setState`在不同场景下的同步和异步行为。此外,文章还提到在实现中要注意动态注册和移除事件监听器以避免内存泄漏。
摘要由CSDN通过智能技术生成

Demo

Demo Link

Note

dropdown 是一种很常见的 component,一般有两种:

  1. 展开 dropdown menu 后,点击任意地方都应该收起 menu。
  2. 展开 dropdown menu 后,点击 menu 内部,不会收起 menu,只有点击 menu 外部,才收起 menu。

在 jQuery 时代,dropdown 是很好实现的,直接用 document.addEventListener('click', handler),监听 document 的 click 事件,然后让 dropdown 的 menu 隐藏起来。如果想让 menu 内部的点击不收起 menu,则让 menu 内部的点击事件执行 event.stopPropagation()

刚开始做 React 开发的时候,不知道是从哪接收到的思想,觉得 document.addEventListener() 的 API 不那么 React,很排斥使用。这样,在实现 dropdown component 时,怎么处理在 menu 以外点击时让 menu 收起来成了一个头疼的问题。

我查了文档,觉得可以用 onBlur 这个事件,但为了能够接收到 onBlur 事件,menu 内部必须是 input 类型的 component,或者是有 tabIndex 属性,然后加上 tabIndex 后,当 component 处于 onFocus 时,会额外在边框上加上阴影的样式,像下图所示,必须额外再加 css 处理。总之,逻辑变得复杂了。

后来用 React 做音乐播放器,看别人的实现源码,发现他们都大都使用了 audioElement.addEventListener('play', handler) 这种原生 API,而且,有些逻辑如果不用原生事件就没法处理,比如监听 window 的 resize 事件,似乎除了用 window.addEventListener('resize', handler) 就没有其它办法了。因此再回过头来看 dropdown 的实现,如果也用 document.addEventListener('click', handler) 处理 menu 以后的点击的话,逻辑就简单多了。

但是,也还是有坑的。

坑之一,React 的 event.stopPropagation() 无法阻止原生事件冒泡到 document。

看这篇文章的详细介绍:

React 的 issue:

React 有两套事件系统,一套是原生事件系统,就是 document.addEventListener() 这种 API,另一套是 React 自己定义的,叫 SyntheticEvent (合成事件),比如下例中的 onClick

<a onClick={this.clickLink}>Open</a>
复制代码

实际 React 的所有合成事件都是绑定在 document 上的 (所谓的代理方式),而不是单独绑在各个 component 上,当你执行合成事件中的 event.stopPropagation() 时,实际原生事件已经到达 document 了。

所以 React 的 event.stopPropagation() 只能阻止合成事件继续往上冒泡,却不能阻止原生事件往上冒泡到 document。

所以你会发现,为什么我已经在 menu 内部的点击事件 handler 中 stopPropagation 了,为什么全局的 click handler 还是会执行,这就是原因。

但是! React 的合成事件的 stopPropagation 虽然不能阻止事件冒泡到 document,但它可以阻止事件冒泡到 window。

(这件事让我想起,在某个项目中,我用了 React 的 event.stopPropagation(),导致 turbolinks 不工作了,当时觉得很理所当然,现在回想,不对,turoblinks 绑定的是原生事件,如果它是绑在 <

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值