React中的合成事件
Synthetic /sɪnˈθetɪk/ Event
合成事件是围绕浏览器原生事件,充当跨浏览器包装器的对象;它们将不同浏览器的行为合并为一个 API,这样做是为了确保事件在不同浏览器中显示一致的属性!
合成事件的基本操作
基础语法
在JSX元素上,直接基于 onXxx={函数} 进行事件绑定!
浏览器标准事件,在React中大部分都支持
developer.mozilla.org/zh-CN/docs/…
import React, { Component } from "react";
export default class App extends Component {
state = {
num: 0
};
render() {
let { num } = this.state;
return <div>
{num}
<br />
<button onClick={(ev) => {
// 合成事件对象 :SyntheticBaseEvent
console.log(ev);
this.setState({
num: num + 1
});
}}>处理</button>
</div>;
}
};
合成事件中的this和传参处理
在类组件中,我们要时刻保证,合成事件绑定的函数中,里面的this是当前类的实例!
也需要保障传参的正常!
import React, { Component } from "react";
export default class App extends Component {
handler1 = (ev) => {
console.log(ev, this);
};
handler2 = (x, y, ev) => {
console.log(x, y, ev, this);
};
render() {
return <div>
<button onClick={this.handler1}>测试1</button>
<button onClick={this.handler2.bind(null, 10, 20)}>
测试2
</button>
</div>;
}
};
JS中的事件委托(事件代理)
事件和事件绑定
-
事件是浏览器内置行为
-
事件绑定是给事件行为绑定方法
- 元素.onxxx=function…
- 元素.addEventListener(‘xxx’,function(){},true/false)
事件的传播机制
- 捕获 CAPTURING_PHASE
- 目标 AT_TARGET
- 冒泡 BUBBLING_PHASE
阻止冒泡传播
- ev.cancelBubble=true 「<=IE8」
- ev.stopPropagation()
- ev.stopImmediatePropagation() 阻止监听同一事件的其他事件监听器被调用
事件委托(代理),就是利用事件的“冒泡传播机制”实现的
例如:给父容器做统一的事件绑定(点击事件),这样点击容器中的任意元素,都会传播到父容器上,触发绑定的方法!在方法中,基于不同的事件源做不同的事情!
- 性能得到很好的提高「减少内存消耗」
- 可以给动态增加的元素做事件绑定
- 某些需求必须基于其完成
const container = document.querySelector('.container'),
box = document.querySelector('.box');
container.addEventListener('click', function (ev) {
let target = ev.target,
targetTag = target.tagName,
targetClass = target.classList;
if (targetTag === 'BUTTON' && targetClass.contains('submit')) {
console.log('我是按钮');
return;
}
if (targetTag === 'A' && targetClass.contains('link')) {
console.log('我是超链接按钮');
return;
}
console.log('我是新增的PBOX');
});
box.onclick = function (ev) {
console.log('我是DIV盒子');
ev.stopPropagation();
};
setTimeout(() => {
const pBox = document.createElement('p');
pBox.className = 'p-box';
pBox.innerHTML = '我是P段落盒子';
container.appendChild(pBox);
}, 2000);
合成事件的底层机制
总原则:基于事件委托实现
React17及以后,是委托给#root元素
const root = document.querySelector('#root'),
outer = document.querySelector('.outer'),
inner = document.querySelector('.inner');
/* 原理 */
const dispatchEvent = function dispatchEvent(ev, isCapture) {
let path = ev.path,
target = ev.target;
if (isCapture) {
[...path].reverse().forEach(elem => {
let handler = elem.onClickCapture;
if (typeof handler === "function") handler(ev);
});
return;
}
path.forEach(elem => {
let handler = elem.onClick;
if (typeof handler === "function") handler(ev);
});
};
root.addEventListener('click', function (ev) {
dispatchEvent(ev, false);
}, false);
root.addEventListener('click', function (ev) {
dispatchEvent(ev, true);
}, true);
......
React17以前,是委托给document元素,并且没有实现捕获阶段的派发
const outer = document.querySelector('.outer'),
inner = document.querySelector('.inner');
/* 原理 */
const dispatchEvent = function dispatchEvent(ev) {
let path = ev.path,
target = ev.target;
[...path].reverse().forEach(elem => {
let handler = elem.onClickCapture;
if (typeof handler === "function") handler(ev);
});
path.forEach(elem => {
let handler = elem.onClick;
if (typeof handler === "function") handler(ev);
});
};
document.addEventListener('click', function (ev) {
dispatchEvent(ev);
}, false);
......
事件对象池
16版本中,存在事件对象池
- 缓存和共享:对于那些被频繁使用的对象,在使用完后,不立即将它们释放,而是将它们缓存起来,以供后续的应用程序重复使用,从而减少创建对象和释放对象的次数,进而改善应用程序的性能!
- 使用完成之后,释放对象「每一项内容都清空」,缓存进去!
- 调用 event.persist() 可以保留住这些值!
17版本及以后,移除了事件对象池!
syntheticInnerBubble = (syntheticEvent) => {
// syntheticEvent.persist();
setTimeout(() => {
console.log(syntheticEvent); //每一项都置为空
}, 1000);
};
click延迟和Vue中的事件处理机制
click事件在移动端存在300ms延迟
- pc端的click是点击事件
- 移动端的click是单击事件
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// 解决移动端300ms延迟
import fastclick from 'fastclick';
fastclick.attach(document.body);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
也可以自己基于touch事件模型去实现
const box = document.querySelector('.box');
box.ontouchstart = function (ev) {
let point = ev.changedTouches[0];
this.startX = point.pageX;
this.startY = point.pageY;
this.isMove = false;
};
box.ontouchmove = function (ev) {
let point = ev.changedTouches[0];
let changeX = point.pageX - this.startX;
let changeY = point.pageY - this.startY;
if (Math.abs(changeX) > 10 || Math.abs(changeY) > 10) {
this.isMove = true;
}
};
box.ontouchend = function (ev) {
if (!this.isMove) {
console.log('点击触发');
}
};
Vue中的事件处理机制
核心:给创建的DOM元素,单独基于
addEventListener实现事件绑定
Vue事件优化技巧:手动基于事件委托处理
<template>
<div id="app">
<ul class="box" @click="handler">
<li
class="item"
v-for="(item, index) in arr"
:data-item="item"
:key="index"
>
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
arr: [10, 20, 30],
};
},
methods: {
handler(ev) {
let target = ev.target;
if (target.tagName === "LI") {
console.log(target.getAttribute("data-item"));
}
},
},
};
</script>