DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>事件委托title>
<link rel="stylesheet" href="src/assets/reset.min.css" />
<style>
html,
body {
height: 100%;
overflow: hidden;
}
.center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#root {
width: 300px;
height: 300px;
background: lightblue;
}
#outer {
width: 200px;
height: 200px;
background: lightgreen;
}
#inner {
width: 100px;
height: 100px;
background: lightcoral;
}
style>
head>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
<div id="inner" class="center">#innerdiv>
div>
div>
body>
html>
<script>
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// 手动做的事件绑定
window.addEventListener("click",() => {console.log(`捕获-window`);},true);
window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
document.addEventListener("click",() => {console.log(`捕获-document`);},true);
document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
html.addEventListener("click",() => {console.log(`捕获-html`);},true);
html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
body.addEventListener("click",() => {console.log(`捕获-body`);},true);
body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
root.addEventListener("click",() => {console.log(`捕获-root`);},true);
root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
inner.addEventListener("click",() => {console.log(`冒泡-inner`);},false);
script>
示例:
结构示例:
DOCTYPE html>
<html>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
<div id="inner" class="center">#innerdiv>
div>
div>
body>
html>
<script>
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// 手动做的事件绑定
window.addEventListener("click",() => {console.log(`捕获-window`);},true);
window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
document.addEventListener("click",() => {console.log(`捕获-document`);},true);
document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
html.addEventListener("click",() => {console.log(`捕获-html`);},true);
html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
body.addEventListener("click",() => {console.log(`捕获-body`);},true);
body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
root.addEventListener("click",() => {console.log(`捕获-root`);},true);
root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
inner.addEventListener("click",() => {console.log(`冒泡-inner`);},false);
script>
执行示例:
点击#inner后:
DOCTYPE html>
<html>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
<div id="inner" class="center">#innerdiv>
div>
div>
body>
html>
<script>
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// 执行顺序:
window.addEventListener("click",() => {console.log(`捕获-window`);},true);
document.addEventListener("click",() => {console.log(`捕获-document`);},true);
html.addEventListener("click",() => {console.log(`捕获-html`);},true);
body.addEventListener("click",() => {console.log(`捕获-body`);},true);
root.addEventListener("click",() => {console.log(`捕获-root`);},true);
outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
inner.addEventListener("click",() => {console.log(`冒泡-inner`);},false);
outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
script>
点击#outer后:
DOCTYPE html>
<html>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
<div id="inner" class="center">#innerdiv>
div>
div>
body>
html>
<script>
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// 执行顺序:
window.addEventListener("click",() => {console.log(`捕获-window`);},true);
document.addEventListener("click",() => {console.log(`捕获-document`);},true);
html.addEventListener("click",() => {console.log(`捕获-html`);},true);
body.addEventListener("click",() => {console.log(`捕获-body`);},true);
root.addEventListener("click",() => {console.log(`捕获-root`);},true);
outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
script>
点击#root后:
DOCTYPE html>
<html>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
<div id="inner" class="center">#innerdiv>
div>
div>
body>
html>
<script>
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// 执行顺序:
window.addEventListener("click",() => {console.log(`捕获-window`);},true);
document.addEventListener("click",() => {console.log(`捕获-document`);},true);
html.addEventListener("click",() => {console.log(`捕获-html`);},true);
body.addEventListener("click",() => {console.log(`捕获-body`);},true);
root.addEventListener("click",() => {console.log(`捕获-root`);},true);
root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
script>
事件传播的特点:
事件传播中的捕获阶段从window到document到html到body,直到事件源的祖先元素。
事件源的捕获与传播就是目标阶段。
事件传播中的捕获阶段从事件源的祖先元素到body,再到html到document到window。
一个DOM元素在事件传播的过程中的每一步都可以绑定多个事件。
<script>
const inner = document.querySelector("#inner");
inner.addEventListener("click",() => {console.log(`冒泡-inner-1`);},false);
inner.addEventListener("click",() => {console.log(`冒泡-inner-2`);},false);
inner.addEventListener("click",() => {console.log(`冒泡-inner`);},false);
script>
<script>
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// 执行顺序:
window.addEventListener("click",() => {console.log(`捕获-window`);},true);
document.addEventListener("click",() => {console.log(`捕获-document`);},true);
html.addEventListener("click",() => {console.log(`捕获-html`);},true);
body.addEventListener("click",() => {console.log(`捕获-body`);},true);
root.addEventListener("click",() => {console.log(`捕获-root`);},true);
outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
inner.addEventListener("click",() => {console.log(`冒泡-inner-1`);},false);
inner.addEventListener("click",() => {console.log(`冒泡-inner-2`);},false);
outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
script>
event.stopPropagation()
:只会阻止下一步的处理,但是对于当前这一步的当前所在DOM元素绑定的多个方法依然都会被触发执行。
<script>
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// 执行顺序:
window.addEventListener("click",() => {console.log(`捕获-window`);},true);
document.addEventListener("click",() => {console.log(`捕获-document`);},true);
html.addEventListener("click",() => {console.log(`捕获-html`);},true);
body.addEventListener("click",() => {console.log(`捕获-body`);},true);
root.addEventListener("click",() => {console.log(`捕获-root`);},true);
outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
inner.addEventListener("click",(event) => {event.stopPropagation();console.log(`冒泡-inner-1`);},false);
inner.addEventListener("click",() => {console.log(`冒泡-inner-2`);},false);
outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
script>
冒泡-inner-1
与冒泡-inner-2
都会打印,但冒泡-outer
及其后方都不会再执行了!event.stopImmediatePropagation()
:不仅会阻止下一步的处理,对于当前这一步的当前所在DOM元素上绑定的其它方法,如果还没有执行,也不会再执行了!
<script>
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// 执行顺序:
window.addEventListener("click",() => {console.log(`捕获-window`);},true);
document.addEventListener("click",() => {console.log(`捕获-document`);},true);
html.addEventListener("click",() => {console.log(`捕获-html`);},true);
body.addEventListener("click",() => {console.log(`捕获-body`);},true);
root.addEventListener("click",() => {console.log(`捕获-root`);},true);
outer.addEventListener("click",() => {console.log(`捕获-outer`);},true);
inner.addEventListener("click",() => {console.log(`捕获-inner`);},true);
inner.addEventListener("click",(event) => {event.stopImmediatePropagation();console.log(`冒泡-inner-1`);},false);
inner.addEventListener("click",() => {console.log(`冒泡-inner-2`);},false);
outer.addEventListener("click",() => {console.log(`冒泡-outer`);},false);
root.addEventListener("click",() => {console.log(`冒泡-root`);},false);
body.addEventListener("click",() => {console.log(`冒泡-body`);},false);
html.addEventListener("click",() => {console.log(`冒泡-html`);},false);
document.addEventListener("click",() => {console.log(`冒泡-document`);},false);
window.addEventListener("click",() => {console.log(`冒泡-window`);},false);
script>
冒泡-inner-1
会打印,但冒泡-inner-2
与冒泡-outer
及其后方都不会再执行了!事件例子:
DOCTYPE html>
<html>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
<div id="inner" class="center">#innerdiv>
div>
div>
body>
html>
<script>
// 点击body中的任意元素,都会触发body的点击事件。
document.body.addEventListener("click", function (ev) {
let target = ev.target;
let targetTag = target.tagName;
let targetId = target.is;
if(targetTag==='BODY'){
console.log(`body`);
return
}
if(targetId==='root'){
console.log(`root`);
return
}
if(targetId==='outer'){
console.log(`outer`);
return
}
console.log(`点击的是非预期元素`);
},false);
// 其它的处理逻辑比较复杂,可以单独绑定。
document.querySelector(`#inner`).addEventListener('click',function(ev){
console.log(`inner,这一行执行的代码比较多。`);
ev.stopPropagation()
},false)
script>
事件委托,也被叫做事件代理。
事件委托就是利用事件的传播机制
来实现的一种项目解决方案
。
例如:一个容器中有很多后代元素,其中大部分后代元素,在点击的时候,都会处理一些事情。有些元素被点击时做相同的事情,有些元素点击时做不同的事情。
传统方案:想操作那些元素,就把这些元素全部都获取到,然后逐一做事件绑定。
DOCTYPE html>
<html>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
<div id="inner" class="center">#innerdiv>
div>
div>
body>
html>
<script>
document.querySelector(`body`).addEventListener("click", function (ev) {
console.log(`body`);
},false);
document.querySelector(`#root`).addEventListener("click", function (ev) {
console.log(`root`);
if(targetId==='outer'){
console.log(`outer`);
return
}
console.log(`点击的是非预期元素`);
},false);
document.querySelector(`#outer`).addEventListener("click", function (ev) {
console.log(`outer`);
},false);
document.querySelector(`#inner`).addEventListener('click',function(ev){
console.log(`inner,这一行执行的代码比较多。`);
ev.stopPropagation()
},false)
script>
新方案:利用事件的传播机制,不逐一获取元素和单独绑定事件了,而是只给外层容器做一个事件绑定,这样不管点击其后代中的那一个元素,当前容器的点击事件也会触发,把容器元素上绑定的方法执行;在容器元素所绑定的方法中,我们只需要判断事件源是谁,从而处理不同的事件即可!===>事件委托。
DOCTYPE html>
<html>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
<div id="inner" class="center">#innerdiv>
div>
div>
body>
html>
<script>
// 点击body中的任意元素,都会触发body的点击事件。
document.body.addEventListener("click", function (ev) {
let target = ev.target;
let targetTag = target.tagName;
let targetId = target.id;
if(targetTag==='BODY'){
console.log(`body`);
return
}
if(targetId==='root'){
console.log(`root`);
return
}
if(targetId==='outer'){
console.log(`outer`);
return
}
console.log(`点击的是非预期元素`);
},false);
// 其它的处理逻辑比较复杂,可以单独绑定。
document.querySelector(`#inner`).addEventListener('click',function(ev){
console.log(`inner,这一行执行的代码比较多。`);
ev.stopPropagation()
},false)
script>
例如:我们容器中的元素不是写死的,而是动态绑定(动态创建)的,而且是会持续动态创建。我们需要在点击每个元素的时候,做一些事情!
新创建元素
,都需要获取新创建元素
,给这些新创建元素
单独做事件绑定!原DOM结构:
DOCTYPE html>
<html>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
div>
div>
body>
html>
后续变成的DOM结构:在#outer添加了#inner。
DOCTYPE html>
<html>
<body>
<div id="root" class="center">#root
<div id="outer" class="center">#outer
<div id="inner" class="center">#innerdiv>
div>
div>
body>
html>
真实项目中,还有很多需求,必须基于事件委托来做。
是否有必要基于事件委托处理
,如果确定有必要,则直接基于事件委托
来处理!import React from "react";
import "./DemoEvent.less";
export default class Demo extends React.Component {
render() {
return (
{
console.log("outer 冒泡「合成」");
}}
onClickCapture={() => {
console.log("outer 捕获「合成」");
}}
>
{
console.log("inner 冒泡「合成」");
}}
onClickCapture={() => {
console.log("inner 捕获「合成」");
}}
>
);
}
componentDidMount() {
// 原生事件的绑定。
document.addEventListener("click",() => console.log("document 捕获"),true);
document.addEventListener("click",() => console.log("document 冒泡"),false);
document.body.addEventListener("click",() => console.log("body 捕获"),true);
document.body.addEventListener("click",() => console.log("body 冒泡"),false);
let root = document.querySelector("#root");
root.addEventListener("click", () => console.log("root 捕获"), true);
root.addEventListener("click", () => console.log("root 冒泡"), false);
let outer = document.querySelector(".outer");
outer.addEventListener("click", () => console.log("outer 捕获"), true);
outer.addEventListener("click", () => console.log("outer 冒泡"), false);
let inner = document.querySelector(".inner");
inner.addEventListener("click", () => console.log("inner 捕获"), true);
inner.addEventListener("click", () => console.log("inner 冒泡"), false);
}
}
React中的合成事件SyntheticEvent
合成事件是围绕浏览器原生事件
,充当跨浏览器包装器
的对象;它们将不同浏览器的行为合并为一个API,这样做是为了确保事件在不同浏览器
中显示一致的属性
!
例如:
{}} onClickCapture={()=>{}} >
事件操作
,进行过特殊的处理
。React18中的合成事件绑定原理-事件委托:
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// React18合成事件原理。
const handlerSyntheticEv = function handlerSyntheticEv(ev){
//
// ...
return ev
}
root.addEventListener("click",function (ev) {
//获取传播路径-默认是从内到外。
// console.log(`ev.path-->`, ev.path);
// console.log(`ev.composedPath()-->`, ev.composedPath());
let path = ev.composedPath();
let syntheticBaseEvent = handlerSyntheticEv(ev)
path.reverse().forEach((elem) => {
if (!elem.hasOwnProperty("onClickCaptrue")) {
return;
}
elem.onClickCaptrue(syntheticBaseEvent);
});
},true);
root.addEventListener("click",function (ev) {
//获取传播路径-默认是从内到外。
let path = ev.composedPath();
let syntheticBaseEvent = handlerSyntheticEv(ev)
path.forEach((elem) => {
if (!elem.hasOwnProperty("onClick")) {
return;
}
elem.onClick(syntheticBaseEvent);
});
},false);
// jsx渲染:
outer.onClick=() => {console.log("outer 冒泡「合成」");}
outer.onClickCapture=() => {console.log("outer 捕获「合成」");}
inner.onClick=() => {console.log("inner 冒泡「合成」");}
inner.onClickCapture=() => {console.log("inner 捕获「合成」");}
DemoEvent.less
.center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.outer {
.center;
width: 200px;
height: 200px;
background: lightblue;
.inner {
.center;
width: 100px;
height: 100px;
background: lightcoral;
}
}
DemoEvent.jsx
import React from "react"
import './DemoEvent.less'
export default class Demo extends React.Component {
render() {
return {
console.log('outer 冒泡「合成」')
}}
onClickCapture={() => {
console.log('outer 捕获「合成」')
}}>
{
// ev 合成事件对象
// ev.nativeEvent 原生事件对象
// 合成事件对象的阻止事件传播:阻止合成事件的传播以及原生事件的传播
// ev.stopPropagation()
// 原生事件对象的阻止事件传播:只能阻止原生的事件传播
// ev.nativeEvent.stopImmediatePropagation()
console.log('inner 冒泡「合成」', ev)
}}
onClickCapture={() => {
console.log('inner 捕获「合成」')
}}>
}
componentDidMount() {
document.addEventListener('click', () => console.log('document 捕获'), true)
document.addEventListener('click', () => console.log('document 冒泡'), false)
document.body.addEventListener('click', () => console.log('body 捕获'), true)
document.body.addEventListener('click', () => console.log('body 冒泡'), false)
let root = document.querySelector('#root')
root.addEventListener('click', () => console.log('root 捕获'), true)
root.addEventListener('click', () => console.log('root 冒泡'), false)
let outer = document.querySelector('.outer')
outer.addEventListener('click', () => console.log('outer 捕获'), true)
outer.addEventListener('click', () => console.log('outer 冒泡'), false)
let inner = document.querySelector('.inner')
inner.addEventListener('click', () => console.log('inner 捕获'), true)
inner.addEventListener('click', (ev) => {
// ev 原生事件对象
// ev.stopImmediatePropagation()
console.log('inner 冒泡')
}, false)
}
}
在jsx视图编译的时候,当遇到某个元素上出现onXxx合成事件绑定,其实并没有给元素做事件绑定,只是给这个元素设置了一个onXxx的私有属性,属性值就是我们绑定的方法。
jsx代码:
{console.log("inner 冒泡「合成」"); }}
onClickCapture={() => {console.log("inner 捕获「合成」");}}
>
渲染的结果:
inner.onClick=函数
inner.onClickCapture=函数
inner.onClick={() => {console.log("inner 冒泡「合成」"); }}
inner.onClickCapture={() => {console.log("inner 捕获「合成」");}}
没有做任何的浏览器事件绑定,仅仅是给inner设置了两个私有属性!
在React内部,会给#root容器的点击行为,做事件绑定,捕获和冒泡阶段都处理了!React所有的视图编译完毕后,都会插入到#root容器中,这样点击任何一个元素,都会把#root的点击行为触发!
root.addEventListener("Xxx", function(){
//获取传播路径。
//按照从外到内的顺序,把元素的onXxxCapture合成事件的私有属性,依次触发执行。
}, true);
root.addEventListener("Xxx", function(){
//获取传播路径。
//按照从内到外的顺序,把元素的onXxx合成事件的私有属性,依次触发执行。
}, false);
事件委托
React中的合成事件对象:
React16中,其合成事件的底层处理机制和React18有很大的区别:
onXxx
/onXxxCapture
私有属性!冒泡阶段
的委托。
const html = document.documentElement;
const body = document.body;
const root = document.querySelector("#root");
const outer = document.querySelector("#outer");
const inner = document.querySelector("#inner");
// React16合成事件原理。
const handlerSyntheticEv = function handlerSyntheticEv(ev){
//
// ...
return ev
}
document.addEventListener("click",function (ev) {
//获取传播路径-默认是从内到外。
//处理合成事件对象;
//把传播路径按照倒序,依次执行元素.onXxxCapture()方法;
//把传播路径按照正序,依次执行元素.onXxx()方法;
// console.log(`ev.composedPath()-->`, ev.composedPath());
},false);
// jsx渲染:
outer.onClick=() => {console.log("outer 冒泡「合成」");}
outer.onClickCapture=() => {console.log("outer 捕获「合成」");}
inner.onClick=() => {console.log("inner 冒泡「合成」");}
inner.onClickCapture=() => {console.log("inner 捕获「合成」");}
DemoEvent.less
.center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.outer {
.center;
width: 200px;
height: 200px;
background: lightblue;
.inner {
.center;
width: 100px;
height: 100px;
background: lightcoral;
}
}
DemoEvent.jsx
import React from "react"
import './DemoEvent.less'
export default class Demo extends React.Component {
render() {
return {
console.log('outer 冒泡「合成」')
}}
onClickCapture={() => {
console.log('outer 捕获「合成」')
}}>
{
// ev 合成事件对象
// ev.nativeEvent 原生事件对象
// 合成事件对象的阻止事件传播:阻止合成事件的传播以及原生事件的传播
// ev.stopPropagation()
// 原生事件对象的阻止事件传播:只能阻止原生的事件传播
// ev.nativeEvent.stopImmediatePropagation()
console.log('inner 冒泡「合成」', ev)
}}
onClickCapture={() => {
console.log('inner 捕获「合成」')
}}>
}
componentDidMount() {
document.addEventListener('click', () => console.log('document 捕获'), true)
document.addEventListener('click', () => console.log('document 冒泡'), false)
document.body.addEventListener('click', () => console.log('body 捕获'), true)
document.body.addEventListener('click', () => console.log('body 冒泡'), false)
let root = document.querySelector('#root')
root.addEventListener('click', () => console.log('root 捕获'), true)
root.addEventListener('click', () => console.log('root 冒泡'), false)
let outer = document.querySelector('.outer')
outer.addEventListener('click', () => console.log('outer 捕获'), true)
outer.addEventListener('click', () => console.log('outer 冒泡'), false)
let inner = document.querySelector('.inner')
inner.addEventListener('click', () => console.log('inner 捕获'), true)
inner.addEventListener('click', (ev) => {
// ev 原生事件对象
// ev.stopImmediatePropagation()
console.log('inner 冒泡')
}, false)
}
}
React16中除了合成事件底层处理机制和React18不同,对于合成事件对象的处理,和React18也是不一样的!
.css
/.less
/.sass
等文件中编写的样式,默认都是全局样式
。
各组件之间编写的样式
发生冲突。
各组件中写的样式
,可以只对当前组件起作用
。
Demo1.jsx
import React from "react";
import './Demo1.less'
export default class Demo1 extends React.Component {
render() {
return (
组件1
span
);
}
}
Demo1.less
.demo {
background: skyblue;
.title {
font-size: 18px;
color: red;
}
}
Demo2.jsx
import React from "react"
import './Demo2.less'
export default class Demo2 extends React.Component {
render() {
return
组件2
}
}
Demo2.less
.demo {
background: skyblue;
.title {
font-size: 18px;
color: red;
}
}
Demo1.jsx
与Demo2.jsx
中的样式用的都是全局的样式。具体那个生效,就看那个的权重及所处的前后位置了。
样式私有化处理方案一:人为有意识地、有规范地规避冲突问题。
使用less
、sass
、stylus
等预编译语言,主要是是利用其提供的嵌套功能。
制定组件命名规范,尤其是组件根节点
样式的命名规范,保证所有组件
在复用多次的情况
下当前组件根节点的样式类名
是唯一的。
使用类似于组件所在的文件夹路径-组件名-box
的命名规范。
/src/components/TabBar.jsx
根节点的样式类名为common-tabbar-box
;/views/home/TableList.jsx
根节点的样式类名为home-table-list-box
;/views/device/Login.jsx
根节点的样式类名为device-table-list-box
;/views/Login.jsx
根节点的样式类名为login-box
;//假设目录为:
|-components
|-TabBar.jsx -> common-tabbar-box
|-...
|-views
|-home
|-TableList.jsx -> home-table-list-box
|-...
|-device
|-TableList.jsx -> device-table-list-box
|-...
|-Login.jsx -> login-box
|-...
而组件内部所有元素的样式,都要保证在该组件的样式类名下编写。
.device-table-list-box{
.title{
...
.num{ ... }
}
span{ ... }
}
如果选择器有过长的前缀,会影响渲染的性能。
//从右到左渲染
.box a{}//先找到所有的a标签,再筛选出哪些在 .box 下。
a{}//找到所有的a标签。所以这个选择器的性能好。
在样式表中编写的样式都是静态样式,无法基于js等业务逻辑控制其动态化。
Demo1.jsx
import React from "react";
import './Demo1.less'
export default class Demo1 extends React.Component {
render() {
return (
组件1
span
);
}
}
Demo1.less
.views-demo1-box {
background: skyblue;
.title {
font-size: 18px;
color: red;
}
}
Demo2.jsx
import React from "react"
import './Demo2.less'
export default class Demo2 extends React.Component {
render() {
return
组件2
}
}
Demo2.less
.views-demo2-box {
background: skyblue;
.title {
font-size: 18px;
color: red;
}
}
样式私有化处理方案二:CSS Modules
。
唯一性
。样式表的命名:xxx.module.css
与xxx.module.less
。如果不使用less后缀与scss后缀等,可以不再使用less/sass等预编译语言了。
在webpack编译的时候,会把.module.css
中所有的样式类名,都进行编译处理,按照规则变为唯一的新式类。
例如:在Demo1.module.css
文件中:
.demo {...}
.title {...}
.title span {...}
经过编译后的样式:
.Demo1_demo__bLprq {...}
.Demo1_title__EpCHF {...}
.Demo1_title__EpCHF span {...}
在组件中使用的时候:
import sty from './Demo1.module.css'
/* sty = {
demo: "Demo1_demo__bLprq",
title: "Demo1_title__EpCHF"
} */
样式私有化处理方案三:基于技术手段,把css写在js中。这样的处理思想,我们称之为css in js
;
React-JSS
。styled-components
,常用的。所有样式都写成内联样式行内样式。
安装vscode插件
:vscode-styled-components
。
使用步骤:
导入插件,基于插件编写样式「相当于创建一个样式组件,其内部包含的是需要的样式」
import styled from "styled-components"
const colorRed = 'red'
const DemoBox = styled.div`
...
.title{
...,
color: ${colorRed};
}
`
视图中,直接基于创建的组件来渲染样式即可
export default class Demo1 extends React.Component {
render() {
return
...
}
}
编译的原理:在渲染DemoBox组件的时候,会被渲染为一个div标签,并且给其设置一个唯一的样式类名。
基于css-in-js
实战开发中的细节问题:
公共样式的设置。
公共样式优先级。
动态样式:
//从右到左渲染
.box a{}//先找到所有的a标签,再筛选出哪些在 .box 下。
a{}//找到所有的a标签。所以这个选择器的性能好。