If you have worked on a JavaScript project either on the front end or the back end, you should be quite familiar with the pattern of attaching events to a document, element, or a window object through the build-in Events module.
如果您在前端或后端都从事过JavaScript项目,那么您应该非常熟悉通过内置“事件”模块将事件附加到文档,元素或窗口对象的模式。
React creates a level of abstraction over this core JS module to expose the so-called Synthetic Event wrapper by which we can manipulate and access the Virtual DOM elements in React. In the majority of cases, that would be all that we need to pass a function to one of the Synthetic Event handlers that React exposes for us.
React在这个核心JS模块上创建了一个抽象级别,以暴露所谓的Synthetic Event包装器,通过它我们可以在React中操作和访问Virtual DOM元素。 在大多数情况下,这就是将函数传递给React为我们提供的其中一个Synthetic Event处理程序所需要的。
There are cases though where we would need something different: something more customized to our needs, something more flexible. There will be cases where the conventional approach of using Synthetic Events or even global state is simply not good enough or quite justified.
在某些情况下,我们需要一些不同的东西:一些更符合我们需求的东西,一些更灵活的东西。 在某些情况下,使用合成事件甚至全局状态的常规方法根本不够好或没有正当理由。
Exactly for those cases, going for the pure JavaScript Event Listener could be the solution we were looking for. This is exactly why we all love React, don’t we? It’s all just JavaScript, at the end of the day.
正是在这些情况下,使用纯JavaScript事件监听器可能是我们正在寻找的解决方案。 这就是我们所有人都喜欢React的原因,不是吗? 到最后,这全都是JavaScript。
This is what you can expect from this article if you continue reading:
如果您继续阅读以下内容,这就是您可以从本文中获得的期望:
- How to add, clean up and monitor correctly event listeners. 如何正确添加,清理和监视事件侦听器。
- Event listener performance and optimizations strategies. 事件侦听器性能和优化策略。
- How and why we would want to create custom event listeners. 我们将如何以及为什么要创建自定义事件侦听器。
- Uses cases that would justify their usage over the more conventional and “Reactish” implementations. 用例可以证明它们比更传统的和“ Reactish”实现更合理的用法。
如何正确添加,清理和监视我们的事件监听器 (How to Add, Clean Up, and Monitor Our Event Listeners Correctly)
Before we dive into the code and implementations, I want to clear out something that I feel is a bit misunderstood about adding events in React and JavaScript in general. We can add an event listener to the window, document, or element objects in our code. Depending on what behaviour are we expecting, we need to consider where exactly to add that listener to.
在深入研究代码和实现之前,我想先清除一下我认为通常在React和JavaScript中添加事件的一些误解。 我们可以在代码中的窗口,文档或元素对象中添加事件侦听器。 根据我们期望的行为,我们需要考虑将侦听器添加到的位置。
In any case, I would strongly advise attaching such listeners to React, in the mounting phase of the component, in order to avoid any unexpected behaviour.
无论如何,我强烈建议在组件的安装阶段将此类侦听器附加到React上,以避免任何意外的行为。
This is how we would do it in a functional component:
这是我们在功能组件中执行的操作:
It is absolutely crucial to clean up and remove our listener after we no longer need them; in this case, that would be when the component unmounts. If we leave listeners to hang around without cleaning them up, there could be serious performance and unexpected behaviour issues for our application, so please treat this matter with extra caution.
在我们不再需要它们之后,清理并删除我们的监听器绝对至关重要。 在这种情况下,就是在卸载组件时。 如果我们在不清理听众的情况下让他们闲逛,那么对于我们的应用程序可能会出现严重的性能和意外的行为问题,因此请格外小心。
Another way to ensure that we have our listener where we want to, or on the contrary, to make sure that we successfully removed the listener after some action, is to check on and monitor our listeners. How can we do that?
确保将侦听器放置在想要的位置的另一种方法,或者相反,确保在执行某些操作后成功删除侦听器,是检查并监视侦听器。 我们该怎么做?
It's quite easy. Here is an example with Chrome Dev Tools:
这很容易。 这是Chrome开发工具的示例:
When we open the developer tools and navigate to the element we are interested in — in our case, the HTML document, because we attached our listener on a document level — we can actually see our event listener attached to the DOM, waiting to be executed.
当我们打开开发人员工具并导航到我们感兴趣的元素时(在本例中为HTML文档),因为我们将侦听器附加在文档级别上,所以实际上我们可以看到事件侦听器已附加到DOM,等待执行。
Since I am using codesandbox.io for the examples, we can see some additional listeners attached there as well. Here is how it would have looked like if we had some listeners on the window, document, and element objects inside the Event Listeners tab:
由于我将codeandbox.io用于示例,因此我们也可以看到附加的其他侦听器。 如果我们在“事件侦听器”选项卡中的窗口,文档和元素对象上有一些侦听器,则情况如下所示:
If you take a closer look of this last screenshot, you will see that except for the additional event listeners on the click event, there are events called “contextmenu” and “auxclick,”; those are definitely not defaults, but custom events added from codesandbox.io to the global event namespace.
如果仔细看一下最后一个屏幕截图,您会发现除了click事件上的其他事件侦听器之外,还有名为“ contextmenu”和“ auxclick”的事件; 这些绝对不是默认值,而是将自定义事件从codeandbox.io添加到全局事件名称空间。
Later on, you will have the chance to see how we can create our own custom events in the section “How and why we would want to create custom event listeners.”
稍后,您将有机会在“如何以及为什么要创建自定义事件侦听器”部分中看到我们如何创建自己的自定义事件。
事件侦听器性能和优化策略 (Event Listener Performance and Optimizations Strategies)
I will emphasize again that in order to avoid any issues with our event listeners, we must properly add (in the mounting phase of our component) and clean-up when we no longer need them (usually in the phase where the component unmounts).
我将再次强调,为了避免事件监听器出现任何问题,我们必须适当地添加(在组件的安装阶段)并在不再需要它们时进行清理(通常是在组件卸载的阶段)。
Additionally, you already saw how to monitor all event listeners via Developer Tools and to double-check if our code executed and behaved the way we expected it. So now that we have a very good understanding of how to handle and observe our listeners, let's see how we can make sure that they are configured for optimal performance.
此外,您已经了解了如何通过开发人员工具监视所有事件侦听器,以及仔细检查我们的代码是否按照我们期望的方式执行和行为。 因此,既然我们对如何处理和观察侦听器有了很好的了解,让我们看看如何确保将其配置为具有最佳性能。
There is a third argument that is available for use on the addEventListener method, which lets us specify some handy options like the once property.
在addEventListener方法上可以使用第三个参数,它使我们可以指定一些方便的选项,例如一次属性。
- once
ABoolean
indicating that thelistener
should be invoked at most once after being added. Iftrue
, thelistener
would be automatically removed when invoked. Mozilla Docs
- once
Boolean
指示在添加listener
后最多应调用一次。 如果为true
,则在调用listener
会自动将其删除。 Mozilla文件
Passing true to the once property can be extremely useful if we have a use case where we want out event listener to be executed only one time. The docs say that when we pass that option, it gets automatically removed after our listener has been triggered, so we don’t have to worry about cleanup.
如果我们有一个用例,希望事件监听器只执行一次 ,那么将true传递给after属性非常有用。 文档说,当我们传递该选项时,它会在触发我们的侦听器后自动删除,因此我们不必担心清理。
Another very handy flag that is available to us is the passive option. With its help, we can greatly improve the performance of our listeners. In a real-world scenario, just by providing this option, I was able to reduce the responsive time of the elements the listener was attached to. This is how it looks:
我们可以使用的另一个非常方便的标志是被动选项。 有了它的帮助,我们可以大大提高听众的表现。 在现实世界中,仅通过提供此选项,我就可以减少侦听器所附加元素的响应时间。 看起来是这样的:
- passive
ABoolean
that, iftrue
, indicates that the function specified bylistener
will never callpreventDefault()
. If a passive listener does callpreventDefault()
, the user agent will do nothing other than generate a console warning. See Improving scrolling performance with passive listeners to learn more.
- passive
Boolean
,如果为true
,则表示listener
指定的函数将永远不会调用preventDefault()
。 如果被动侦听器确实调用了preventDefault()
,则用户代理将不执行任何操作,只生成控制台警告。 请参阅使用被动侦听器提高滚动性能以了解更多信息。
I was curious to check the performance of a listener with and without this option. I’ve attached 100 events to a document, and by pressing a button the title of that button was replaced with another title. It’s a very simple example, but the idea was this test to be very simple and reproducible. The results were quite decisive!
我很好奇是否使用此选项来检查监听器的性能。 我已将100个事件附加到文档,然后通过按一个按钮将该按钮的标题替换为另一个标题。 这是一个非常简单的示例,但想法是该测试非常简单且可重复。 结果是非常决定性的!
With a regular event listener, the execution time was with an average of 164ms, but when I passed the passive option the execution time dropped to an average of 116ms, which is almost 30% better performance with React! 🤯
对于常规的事件侦听器,执行时间平均为164ms,但是当我通过被动选项时,执行时间平均降至116ms,这几乎比React的性能高30%! 🤯
我们如何以及为什么要创建自定义事件侦听器 (How and Why We Would Want to Create Custom Event Listeners)
Although we have a total of 83 supported events provided to us by React, sometimes we might end up into the position where none of them quite cover our needs. Maybe we need to handle a more complex animation or a custom scroll event that triggers some UI change, or we just want to avoid cluttering and possible overlap of click events on the global namespace.
尽管React总共提供了83个受支持的事件,但有时我们可能最终无法满足我们的需求。 也许我们需要处理更复杂的动画或触发某些UI更改的自定义滚动事件,或者我们只是想避免全局名称空间上的混乱和单击事件可能重叠。
Well, for whatever reason, you might want to avoid to use any of the supported events in React we can always create custom events on our own. It’s actually easier than you might think it is. Let me show you:
嗯,无论出于何种原因,您可能都想避免使用React中任何受支持的事件,我们总是可以自行创建自定义事件。 实际上,它比您想象的要容易。 让我给你演示:
So inside the createCustomEvent
method is where most of the “magic” happens. It’s quite simple, actually — let me walk you through it quickly.
因此在createCustomEvent
内部 方法 是大多数“魔术”发生的地方。 实际上,它非常简单-让我快速引导您。
We expect two parameters to be passed to our
createCustomEvent
method: the event name of your choice and any additional data that you would want to pass whenever this event is called. The place where this additional data lives is within the detail parameter, so that is the place where you need to search it for.我们期望将两个参数传递给我们的
createCustomEvent
方法:您选择的事件名称以及您希望在调用此事件时传递的所有其他数据。 这些附加数据所在的位置在detail参数内,因此这是您需要搜索它的位置。Inside the method body, we first check if the window object exists. If it doesn’t, then our event cannot be attached thus we return null. If windows are defined then we return the CustomEvent constructor, which creates our custom event and passes any data that we want to have inside the detail object.
在方法主体内部,我们首先检查window对象是否存在。 如果不是,则无法附加事件,因此返回null。 如果定义了窗口,则返回CustomEvent构造函数 ,该构造函数创建我们的自定义事件,并传递要在detail对象中包含的所有数据。
Most of the work is done, we need one more method that will dispatch our newly created event. Here is how it looks:
大多数工作已经完成,我们还需要另外一种方法来分派新创建的事件。 外观如下:
With this two-liner method, our job here is done. We are locked and loaded to create and dispatch toggleDrawer
custom event. Let’s just imagine that we need this event listener to handle a drawer that we need to open/close.In this case, we dispatch our event on a document level, but you are free to do it on the window or element object instead.
使用这种两层方法,我们的工作就完成了。 我们被锁定并加载以创建和调度toggleDrawer
自定义事件。 假设我们需要此事件侦听器来处理需要打开/关闭的抽屉,在这种情况下,我们将事件分派到文档级别,但是您可以随意在窗口或元素对象上进行操作。
A quick toggleSideDrawerHandler walkthrough:
快速的toggleSideDrawerHandler演练:
First, we expect to receive a parameter with the
toggleSideDrawerHandler
, this data will be later passed on with our event call.首先,我们希望通过
toggleSideDrawerHandler
接收一个参数,该数据将在以后的事件调用中传递。Second, we invoke the
createCustomEvent
method that we already defined and pass our additional data and the name of our event.其次,我们调用
createCustomEvent
我们已经定义的方法,并传递其他数据和事件的名称。And last, we will dispatch our newly created
toggleDrawer
event whenever this method gets called.最后,每当调用此方法时,我们将调度我们新创建的
toggleDrawer
事件。
After we have created our custom event methods, the only thing left to be done is to use those in action. Now we can attach the toggleSideDrawerHandler
method to any element, and after being invoked, it will dispatch the toggleDrawer event. Here you can find a Code Sandbox with a working implementation of the above example.
创建自定义事件方法后,剩下要做的就是使用这些方法。 现在我们可以附加toggleSideDrawerHandler
方法传递给任何元素,并在调用之后将调度toggleDrawer事件。 在这里,您可以找到带有上述示例的有效实现的Code Sandbox。
用例将证明其在更常规和“重新整理”的实现中的使用合理性 (Use Cases That Would Justify Their Usage Over the More Conventional and “Reactish” Implementations)
Using Event Listeners in React is not something that we want to overuse in general. In the best-case scenario, we would want to use the default Synthetic Event behaviour provided by React; this is the best practice and the way to go.
通常,我们不想在React中使用事件监听器。 在最佳情况下,我们将要使用React提供的默认“合成事件”行为; 这是最佳做法,也是可行的方法。
There are exceptions and cases where this is just not optimal. And for those cases, it’s good to have it on the back of your mind that using an Event Listener is an option. Imagine a case where we want to trigger an action on the other side of our application, and passing down our method via prop drilling is just not possible, or implementing global state management would be an overkill and not really justified. In that case, using a simple Event Listener could be just the right thing to do.
在某些情况下,这并非最佳选择。 对于这些情况,最好让您想到使用事件监听器是一种选择。 想象一下这样一种情况:我们想在应用程序的另一端触发一个动作,并且根本不可能通过prop钻探传递我们的方法,否则实施全局状态管理将是一个过大的决定,并且没有真正的道理。 在这种情况下,使用简单的事件侦听器可能是正确的选择。
Enjoy and happy hacking!
享受和快乐的黑客!
翻译自: https://medium.com/better-programming/master-your-react-skills-with-event-listeners-ebc01dde4fad