
壹 ❀ 引
在前面两篇文章中,我们花了较大的篇幅介绍react的setState方法,在介绍setState同步异步时提到,在react合成事件中react对于this.state更新都是异步,但在原生事件中更新却是同步,这说明react在合成事件处理上必定与原生事件存在部分差异,那么本篇文章就来着重介绍react中的合成事件,在文章开始之前我们先罗列部分问题:
- 了解原生事件监听机制吗?为什么需要事件代理?
- react如何实现的合成事件?这么做的好处是什么?
- 合成事件与原生事件执行先后顺序。
- 合成事件阻止冒泡会阻塞原生事件吗?原生事件阻止冒泡会阻塞合成事件吗?
在文章开始前,大家可以先自行思考这些问题,假设这些在面试中遇到,你能回答多少呢?那么本文开始。
贰 ❀ 从原生事件说起
虽然本文的核心重点是介绍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

最低0.47元/天 解锁文章
959

被折叠的 条评论
为什么被折叠?



