1.react中的合成事件
推荐使用箭头函数,不用管this,但是如果要传参,还得通过bind,事件对象永远是最后一个参数
import React from "react";
class Demo extends React.Component {
/*
基于React内部的处理,如果我们给合成事件绑定一个“普通函数”,当事件行为触发,绑定的函数执行;方法中的this会是undefined「不好」!! 解决方案:this->实例
+ 我们可以基于JS中的bind方法:预先处理函数中的this和实参的
+ 推荐:当然也可以把绑定的函数设置为“箭头函数”,让其使用上下文中的this「也就是我们的实例」
合成事件对象SyntheticBaseEvent:我们在React合成事件触发的时候,也可以获取到事件对象,只不过此对象是合成事件对象「React内部经过特殊处理,把各个浏览器的事件对象统一化后,构建的一个事件对象」
合成事件对象中,也包含了浏览器内置事件对象中的一些属性和方法「常用的基本都有」
+ clientX/clientY
+ pageX/pageY
+ target
+ type
+ preventDefault
+ stopPropagation
+ ...
+ nativeEvent:基于这个属性,可以获取浏览器内置『原生』的事件对象
+ ...
*/
handle1() { //Demo.prototype => Demo.prototype.handle=function handle(){}
console.log(this); //undefined
}
handle2(x, y, ev) {
// 只要方法经过bind处理了,那么最后一个实参,就是传递的合成事件对象!!
console.log(this, x, y, ev); //实例 10 20 合成事件对象
}
handle3 = (ev) => { //实例.handle3=()=>{....}
console.log(this); //实例
console.log(ev); //SyntheticBaseEvent 合成事件对象「React内部经过特殊处理,把各个浏览器的事件对象统一化后,构建的一个事件对象」
};
handle4 = (x, ev) => {
console.log(x, ev); //10 合成事件对象
};
render() {
/*
bind在React事件绑定的中运用
+ 绑定的方法是一个普通函数,需要改变函数中的this是实例,此时需要用到bind「一般都是绑定箭头函数」
+ 想给函数传递指定的实参,可以基于bind预先处理「bind会把事件对象以最后一个实参传递给函数」
*/
return <div>
<button onClick={this.handle1}>按钮1</button>
<button onClick={this.handle2.bind(this, 10, 20)}>按钮2</button>
<button onClick={this.handle3}>按钮3</button>
<button onClick={this.handle4.bind(null, 10)}>按钮4</button>
</div>;
}
}
export default Demo;
2. 事件及事件委托
1 捕获、2目标、3冒泡
使用事件委托以后,如果某个事件直接绑定到指定元素了不需要委托,那么直接在该元素的事件中阻止冒泡即可event.stopPropagation()
/*
事件委托:利用事件的传播机制,实现的一套事件绑定处理方案
例如:一个容器中,有很多元素都要在点击的时候做一些事情
传统方案:首先获取需要操作的元素,然后逐一做事件绑定
事件委托:只需要给容器做一个事件绑定「点击内部的任何元素,根据事件的冒泡传播机制,都会让容器的点击事件也触发;我们在这里,根据事件源,做不同的事情就可以了;」
优势:
+ 提高JS代码运行的性能,并且把处理的逻辑都集中在一起!!
+ 某些需求必须基于事件委托处理,例如:除了点击xxx外,点击其余的任何东西,都咋咋咋...
+ 给动态绑定的元素做事件绑定
+ ...
限制:
+ 当前操作的事件必须支持冒泡传播机制才可以
例如:mouseenter/mouseleave等事件是没有冒泡传播机制的
+ 如果单独做的事件绑定中,做了事件传播机制的阻止,那么事件委托中的操作也不会生效!!
*/
const body = document.body;
body.addEventListener('click', function (event) {
// event.target:事件源「点击的是谁,谁就是事件源」
let target = event.target,
id = target.id;
if (id === "root") {
console.log('root');
return;
}
if (id === "inner") {
console.log('inner');
return;
}
if (id === "AAA") {
console.log('AAA');
return;
}
// 如果以上都不是,我们处理啥....
});
const outer = document.querySelector('#outer');
outer.addEventListener('click', function (event) {
console.log('outer');
event.stopPropagation();
});
3.react事件委托原理
/*
React中合成事件的处理原理
“绝对不是”给当前元素基于addEventListener单独做的事件绑定,React中的合成事件,都是基于“事件委托”处理的!
+ 在React17及以后版本,都是委托给#root这个容器「捕获和冒泡都做了委托」;
+ 在17版本以前,都是为委托给document容器的「而且只做了冒泡阶段的委托」;
+ 对于没有实现事件传播机制的事件,才是单独做的事件绑定「例如:onMouseEnter/onMouseLeave...」
在组件渲染的时候,如果发现JSX元素属性中有 onXxx/onXxxCapture 这样的属性,不会给当前元素直接做事件绑定,只是把绑定的方法赋值给元素的相关属性!!例如:
outer.onClick=() => {console.log('outer 冒泡「合成」');} //这不是DOM0级事件绑定「这样的才是 outer.onclick」
outer.onClickCapture=() => {console.log('outer 捕获「合成」');}
inner.onClick=() => {console.log('inner 冒泡「合成」');}
inner.onClickCapture=() => {console.log('inner 捕获「合成」');}
然后对#root这个容器做了事件绑定「捕获和冒泡都做了」
原因:因为组件中所渲染的内容,最后都会插入到#root容器中,这样点击页面中任何一个元素,最后都会把#root的点击行为触发!!
而在给#root绑定的方法中,把之前给元素设置的onXxx/onXxxCapture属性,在相应的阶段执行!!
*/
import React, { Component, PureComponent} from 'react'
import PropTypes from 'prop-types'
class Demo extends React.Component {
render () {
return <div className="outer"
onClick={
() => {
console.log('outer 冒泡 -- 合成')
}
}
onClickCapture={
() => {
console.log('outer 捕获 -- 合成')
}
}
>
<div className="inner"
onClick={
() => {
console.log('inner 冒泡 -- 合成')
}
}
onClickCapture={
() => {
console.log('inner 捕获 -- 合成')
}
}
></div>
</div>
}
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)
}
}
在页面中点击inner后的顺序
合成事件的原理
react16中的事件委托触发顺序
React中合成事件的处理原理
在16版本中,合成事件的处理机制,不再是把事件委托给#root元素,而是委托给document元素,并且只做了冒泡阶段的委托;在委托的方法中,把onXxx/onXxxCapture合成事件属性进行执行!!
React16中,关于合成事件对象的处理,React内部是基于“事件对象池”,做了一个缓存机制!!React17及以后,是去掉了这套事件对象池和缓存机制的!!
+ 当每一次事件触发的时候,如果传播到了委托的元素上「document/#root」,在委托的方法中,我们首先会对内置事件对象做统一处理,生成合成事件对象!!
在React16版本中:
为了防止每一次都是重新创建出新的合成事件对象,它设置了一个事件对象池「缓存池」
+ 本次事件触发,获取到事件操作的相关信息后,我们从 事件对象池 中获取存储的合成事件对象,把信息赋值给相关的成员!
+ 等待本次操作结束,把合成事件对象中的成员信息都清空掉,再放入到 事件对象池 中!!
原生执行完才执行合成事件
4.解决移动端的点击事件300ms延迟
import React from "react";
class Demo extends React.Component {
// 手指按下:记录手指的起始坐标
touchstart = (ev) => {
let finger = ev.changedTouches[0]; //记录了操作手指的相关信息
this.touch = {
startX: finger.pageX,
startY: finger.pageY,
isMove: false
};
};
// 手指移动:记录手指偏移值,和误差值做对比,分析出是否发生移动
touchmove = (ev) => {
let finger = ev.changedTouches[0],
{ startX, startY } = this.touch;
let changeX = finger.pageX - startX,
changeY = finger.pageY - startY;
if (Math.abs(changeX) > 10 || Math.abs(changeY) > 10) {
this.touch.isMove = true;
}
};
// 手指离开:根据isMove判断是否是点击
touchend = () => {
let { isMove } = this.touch;
if (isMove) return;
// 说明触发了点击操作
console.log('点击了按钮');
};
render() {
return <div>
<button onTouchStart={this.touchstart}
onTouchMove={this.touchmove}
onTouchEnd={this.touchend}>
提交
</button>
</div>;
}
}
export default Demo;
放在业务组件下面
循环创建的元素循环绑定事件 --> 在react中没问题,因为react本身就是通过委托事件来实现的
import React from "react";
class Demo extends React.Component {
state = {
arr: [{
id: 1,
title: '新闻'
}, {
id: 2,
title: '体育'
}, {
id: 3,
title: '电影'
}]
};
handle = (item) => {
// item:点击这一项的数据
console.log('我点击的是:' + item.title);
};
render() {
let { arr } = this.state;
return <div>
{arr.map(item => {
let { id, title } = item;
return <span key={id}
style={{
padding: '5px 15px',
marginRight: 10,
border: '1px solid #DDD',
cursor: 'pointer'
}}
onClick={this.handle.bind(this, item)}>
{title}
</span>;
})}
</div>;
}
}
export default Demo;