文章借鉴 pingan8787 React合成事件 和 React合成事件官方文档
React合成事件是React 模拟原生DOM事件所有能力 的一个事件对象。
根据 W3C规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原声事件相同的接口。
合成事件除了拥有和浏览器原生事件相同的接口,包括stopPropagation()
和preventDefault()
;
⚠️ 在React中,所有事件都是合成的,不是原生DOM事件,可以通过 e.nativeEvent
属性获取原生DOM事件。合成事件不会映射到原生事件;例如:在onMouseLeave
事件中event.nativeEvent
将指向mouseout
事件;
const handleClick = (e) => console.log(e.nativeEvent);;
const button = <button onClick={handleClick}>Leo 按钮</button>
浏览器兼容,实现更好的跨平台
顶层事件代理机制:保证冒泡一致性,可以跨浏览器执行。将不同平台事件模拟成合成事件;
避免垃圾回收
React引入事件池,在事件池中获取或释放事件对象;
React事件对象不会被释放掉,而是存入一个数组中;当事件触发,就从这个数组中弹出,避免频繁地创建和销毁(垃圾回收);
方便事件统一管理和事务机制
e.stopPropagation()
或者e.cancleBubble=true
(IE)来阻止事件等等冒泡传播。// 原生事件 事件处理函数写法
<button onclick="handleClick()">Leo 按钮命名</button>
// React 合成事件 事件处理函数写法
const button = <button onClick={handleClick}>Leo 按钮命名</button>
// 原生事件阻止默认行为方式
<a href="https://www.pingan8787.com"
onclick="console.log('Leo 阻止原生事件~'); return false"
>
Leo 阻止原生事件
</a>
// React 事件阻止默认行为方式
const handleClick = e => {
e.preventDefault();
console.log('Leo 阻止原生事件~');
}
const clickElement = <a href="https://www.pingan8787.com" onClick={handleClick}>
Leo 阻止原生事件
</a>
e.stopPropagation()
和 e.preventDefault()
。Capture
;例如:处理捕获阶段的点击事件请使用 onClickCapture
而不是 onClick
。合成事件的执行顺序
在React中,“合成事件” 会以事件委托的方式绑定到组件最上层,并在组件卸载阶段自动销毁绑定的事件。
原生事件 —— > React事件 —— > document事件
在页面上点击按钮,事件开始在原生DOM上走捕获冒泡流程,React监听的是document上的冒泡阶段;事件冒泡到document后,React将事件再派发到组件树中,然后事件开始在组件树DOM中走捕获冒泡流程。(React上监听的是document上的事件)
同一元素如果对同一类型的事件绑定来多个处理器,会按绑定的顺序来执行;
document.addEventListener()
中的 useCapture
参数为 false;<button onclick="btnClickHandler(event)">CLICK ME</button>
<script>
document.addEventListener("click", function(event) {
console.log("document clicked");
}); //
function btnClickHandler(event) {
console.log("btn clicked");
}
</script>
以上全部都是原生代码,所以输出:(最后冒泡到document)
btn clicked
document clicked
stopPropagation()
可阻止事件往上冒泡,可以实现想要的元素处理该事件,而其他元素接收不到。(原生事件如果执行了stopPropagation
,所有元素的事件将无法冒泡到document
,这样的话,所有的React事件都将无法被注册。)同一元素上同一类型的事件(比如:click事件)绑定来多个事件处理器,本来处理器按绑定的先后顺序来执行,但是如果其中一个调用了stopImmediatePropagation
,不但会阻止事件冒泡,还会组织这个元素后续其他事件处理器的执行。
<button onclick="btnClickHandler(event)">CLICK ME</button>
<script>
document.addEventListener(
"click",
function(event) {
console.log("document clicked");
},
false
);
function btnClickHandler(event) {
event.stopPropagation();
console.log("btn clicked");
}
</script>
输出:
btn clicked
e.stopPropagation()
只能阻止合成事件间冒泡,即下层的合成事件,不会冒泡到上层的合成事件。事件本身还都是在 document 上执行。所以最多只能阻止 document 事件不能再冒泡到 window 上。
(document事件不执行:原生——React事件(e.stopPropagation())——document事件)
在React中,一个组件只能绑定一个同类型的事件监听器,当重复定义时,后面的监听器会覆盖之前的;
事实上 nativeEvent
的 stopImmediatePropagation
只能阻止绑定在 document 上的事件监听器。而合成事件上的e.nativeEvent.stopImmediatePropagation()
能阻止合成事件不会冒泡到 document 上。
事件池
合成事件对象池,是 React 事件系统提供的一种性能优化方式。合成事件对象在事件池统一管理,不同类型的合成事件具有不同的事件池。
事件池未满:React创建新的事件对象,派发给组件;
事件池装满:React从事件池中复用事件对象,派发给组件;
合成事件对象的事件处理函数全部被调用之后,所有属性都会被置为 null
e.persist()
将不再生效;因为合成事件不再放入事件池中;
JSX回调函数中的this经常会出问题,在class方法不会默认绑定this;
解决办法:
this.clickFun = this.clickFun.bind(this);
class App extends React.Component<any, any> {
constructor(props: any) {
super(props);
this.clickFun = this.clickFun.bind(this);
}
clickFun() {
console.log("React this 指向问题");
}
render() {
return (
<div onClick={this.clickFun}>React this 指向问题</div>
);
}
}
export default App;
clickFun = () => {}
class App extends React.Component<any, any> {
clickFun = () => {
console.log("React this 指向问题");
}
render() {
return (
<div onClick={this.clickFun}>React this 指向问题</div>
);
}
}
export default App;
class App extends React.Component<any, any> {
// 省略其他代码
clickFun() {
console.log("React this 指向问题");
}
render() {
return (
<div onClick={() => this.clickFun()}>React this 指向问题</div>
);
}
}
export default App;
const List = [1,2,3,4];
class App extends React.Component<any, any> {
// 省略其他代码
clickFun (id) {console.log('当前点击:', id)}
render() {
return (
<div>
<h1>第一种:通过 bind 绑定 this 传参</h1>
{
List.map(item =>
<div onClick={this.clickFun.bind(this, item)}>
按钮:{item}
</div>
)
}
<h1>第二种:通过箭头函数绑定 this 传参</h1>
{
List.map(item =>
<div onClick={() => this.clickFun(item)}>
按钮:{item}
</div>
)
}
</div>
);
}
}
export default App;
onFocus
事件在元素聚焦时被调用;比如:用户点击文本输入框,就会调用该事件;onBlur
事件在失去焦点时被调用;比如:当用户在已聚焦的文本输入框外点击时,就会被调用;currentTarget
和relatedTarget
来区分聚焦和失去焦点是否来自父元素外部;下面的例子展示来如何监听一个子元素的聚焦、元素本身的聚焦、以及整个子树进入焦点或离开焦点。<div
tabIndex={1}
onFocus={(e) => {
// currentTarget 指的是当前节点
if (e.currentTarget === e.target) {
console.log('focused self');
} else {
console.log('focused child', e.target);
}
if (!e.currentTarget.contains(e.relatedTarget)) {
// Not triggered when swapping focus between children
console.log('focus entered self');
}
}}
>
<input id="1" />
<input id="2" />
</div>
onChange、onInput、onReset、onSubmit、onInvalid
onError onLoad
onMouseEnter
、onMouseLeave
这两个事件从离开的元素向进入的元素传播,不是正常的冒泡,也没有捕获阶段;onMOuseMove onMouseOut onMouseOver onMouseUp
onMouseEnter onMouseLeave
onPointerEnter
和 onPointerLeave
事件从离开的元素向进入的元素传播,不是正常的冒泡,也没有捕获阶段。