在文章开始前,大家可以先自行思考这些问题,假设这些在面试中遇到,你能回答多少呢?那么本文开始。
虽然本文的核心重点是介绍react
的合成事件,但文章开头既然给了合成事件与原生事件相关对比问题,因此了解原生事件是有必要的,考虑到可能有同学对此类知识存在遗忘,这里做个简单复习。
首先让我们复习下事件监听api addEventListener
,基本语法如下:
element.addEventListener(event, function, useCapture);
其中element
表示你需要监听的dom
元素;event
表示监听的事件类型,比如onclick,onblur
;function
表示触发事件后的回调,你需要做什么都可以写在这里;useCapture
是一个布尔值,表示是否开启捕获阶段,默认false
。
以如下代码结构为例,当我们点击span
时,必然会经历过捕获阶段----》目标阶段----》冒泡阶段
:
我们用一个例子复习这个过程:
<div id="div">
我是div
<p id="p">
我是p
<span id="span">我是span</span>
</p>
</div>
const div = document.querySelector("#div");
const p = document.querySelector("#p");
const span = document.querySelector("#span");
// 捕获阶段,这里将useCapture设置为true
div.addEventListener("click",()=>console.log("捕获阶段--div"),true);
p.addEventListener("click",()=>console.log("捕获阶段--p"),true);
// 目标阶段
span.addEventListener("click",()=>console.log("目标阶段--span"));
// 冒泡阶段,useCapture默认false,不写了
div.addEventListener("click",()=>console.log("捕获阶段--div"));
p.addEventListener("click",()=>console.log("捕获阶段--p"));
既然提到了事件监听,那么有三个API
就不得不提了,它们分别是event.preventDefault
、event.stopPropagation
与event.stopImmediatePropagation
。让我们先聊聊stopPropagation
,此方法一般用于阻止冒泡,比如父子都绑定了点击事件,但点击子时我不希望父在冒泡阶段也被触发,因此通过在子的事件回调中添加此方法能做到这一点,修改上述例子中目标阶段的代码为:
span.addEventListener("click", (e) => {
e.stopPropagation();
console.log("目标阶段--span")
});
此时点击span
会发现只会输出span
,而冒泡阶段的div
与p
都被阻止执行了。
关于event.preventDefault
,此方法常用于阻止元素默认行为,比如点击a
标签除了执行我们绑定的click
事件外,它还会执行a
标签默认的跳转。再或者form
表达点击提交会将form
的值传递给action
指定地址并刷新页面,像这类行为我们均可以通过preventDefault
阻止。
在介绍stopImmediatePropagation
之前,我们需要知道事件监听相对于普通事件绑定的一大好处是,事件监听支持为同一dom
监听多个行为,但如果是普通的事件绑定后者会覆盖前者:
span.onclick = ()=>console.log('事件绑定-1');
// 后绑定的事件会覆盖前面的绑定
span.onclick = ()=>console.log('事件绑定-2');
// 事件监听就不会存在覆盖,下面2个都会执行
span.addEventListener("click", (e) => {
console.log("事件监听-1")
});
span.addEventListener("click", (e) => {
console.log("事件监听-2")
});
那既然事件监听支持为同一dom
绑定多个,我在执行了某个监听后,需要将其它监听都阻止掉怎么办?此时就轮到stopImmediatePropagation
出场立大功了,看个例子:
// 捕获阶段
div.addEventListener("click", () => console.log("捕获阶段--div-1"), true);
div.addEventListener("click", () => console.log("捕获阶段--div-2"), true);
p.addEventListener("click", () => console.log("捕获阶段--p-1"), true);
p.addEventListener("click", () => console.log("捕获阶段--p-2"), true);
// 目标阶段
span.addEventListener("click", (e) => {
e.stopImmediatePropagation();
console.log("目标阶段---span-1")
});
span.addEventListener("click", (e) => {
console.log("目标阶段---span-2")
});
// 冒泡阶段
div.addEventListener("click", () => console.log("捕获阶段--div-1"));
div.addEventListener("click", () => console.log("捕获阶段--div-2"));
p.addEventListener("click", () => console.log("捕获阶段--p-1"));
p.addEventListener("click", () => console.log("捕获阶段--p-2"));
可以看到stopImmediatePropagation
同样会阻止事件冒泡,但除此之外,它还会阻止同一dom
身上的其它事件执行。
那么聊完事件监听,什么是事件代理?在现实生活中,我们网购到公司的快递大部分都会由前台代签收,而不是分别送到我们每个人手上,此时前台就相当于做了一个代理的事情,原本需要不同的多个人分别签收的行为,统一与前台代理处理。
映射到代码中,假设有ul>li
的结构,我们希望点击li
显示出li
的文本内容,如果给每个li
绑定就得这么写:
<ul id="ul">
<li onclick="handleClick(event)">1</li>
<li onclick="handleClick(event)">2</li>
<li onclick="handleClick(event)">3</li>
<li onclick="handleClick(event)">4</li>
<li onclick="handleClick(event)">5</li>
</ul>
const handleClick=(e)=>{
console.log(e.target.innerHTML);
};
但如果通过事件代码,我们将点击行为委托给li
共同的父元素ul
,代码将更为清晰简单:
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
const span = document.getElementById("span");
const handleClick = (e) => {
span.innerHTML =`此时点击的是第${e.target.innerHTML}个li`;
};
const ul = document.querySelector("#ul");
ul.addEventListener("click", handleClick)
虽然事件被代理给了ul
,但通过event.target
我们还是能拿到实际操作的li
。这让我想起了17我还在写JQ的年代,当li
是动态遍历生成时,如果给li
绑事件你会发现此时li
其实是不存在的,这就导致事件绑定最终失败,而事件代理在性能更优的前景下更是巧妙解决了这一问题。
OK,关于原生事件监听与事件代理我们就介绍到这里,这部分知识也利于我们理解react
的合成事件。
如果您发现该资源为电子书等存在侵权的资源或对该资源描述不正确等,可点击“私信”按钮向作者进行反馈;如作者无回复可进行平台仲裁,我们会在第一时间进行处理!
加入交流群
请使用微信扫一扫!