事件的本质
在React中,组件的事件,本质上就是组件的一个属性(函数)
按照之前react对组件的约定,由于事件本质上是一个属性,因此也需要使用小驼峰命名法。
注意:自定义组件上声明的事件,就是props里的东西,如果没有调用就不会触发!!
<Tick
onClick={()=>{
console.log("点击了!")
}}
onOver={()=>{
this.setState({
isOver: true
})
}} number={3}
/>
点击Tick会不会生效?会不会?当然不会了!!它不会像内置组件(原生html元素)那样点击后可以自动触发
事件处理中的this
在事件处理函数中,如果没有特殊处理,this默认指向undefined
解决方案
- 在constructor(也可以在组件上,但写在contructor中效率更高,因为组件重新渲染一次就会导致那些东西重新生成)中使用bind更改事件处理函数的this指向,使其指向组件实例
- 事件处理函数写为箭头函数(可以放在组件上,也可以放在render外,字段初始化器,this绑定在实例上)
React中的事件—扩展
跟一些原生操作DOM的第三方库混用的时候,需要特别注意,React会有些“怪异”现象!
这里的事件指的是:React内置的DOM组件中的事件,这个前提非常非常重要!!
要记住:React允许其虚拟DOM树与真实DOM有差异,可通过React开发者工具查看比对
代码
//App.js
import React from 'react'
console.log(1111);
//下述"前后"代表,是在ReactDOM.render()前还是后为原生DOM注册事件
document.querySelector("#root").onclick = function(e){
console.log("真实的DOM事件:id为root的div被点击了");
// e.stopPropagation();//前后都是:仅仅document的click被阻止了
}
document.addEventListener("click", function(e){
console.log("真实DOM:document被点击了!");
});
export default function App() {
//下述测试前提是,真实dom注册事件是在ReactDOM.render(---->index.js中)之前执行的
console.log(44444);
return (
<div onClick={()=>{
console.log("react: div被点击了")
}}>
<button onClick={e=>{
console.log("react: button被点击了");
console.log(e, e.nativeEvent);
console.log(e.isPropagationStopped());
// e.nativeEvent.stopImmediatePropagation();//前:仅document的click被阻止了
//后: root的click和document的click都被阻止了
// e.nativeEvent.stopPropagation();//这是没有意义的因为已经冒泡到了document了
//前后都是:仅仅document的click被阻止了,其余没影响
// e.stopPropagation();//前后都是:父亲div的click和document的click都被阻止了
console.log(e.isPropagationStopped());
}}>
按钮
</button>
</div>
)
}
console.log(22222);
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
console.log(3333)
ReactDOM.render((
<App/>
), document.getElementById('root'));
console.log(5555);
原理
- 最终是给document(真实DOM中的document)注册事件,这是为了提升效率
- 几乎所有(因为有些元素的事件本身就是不会冒泡的,比如input的focus)元素的事件处理,均在document的事件中处理(原生js事件会冒泡捕获嘛,而react是直接在document统一处理,有个队列保证执行顺序)
- 一些不冒泡的事件,是直接在元素上监听
- 一些document上面没有的事件,直接在元素上监听
- 在document的事件处理中,React会根据虚拟DOM树依次完成事件处理函数的调用,最后再调用自己的事件处理函数
React的事件参数,并非真实的DOM事件参数,是React合成的一个对象,它类似于真实DOM的事件参数
如果给真实的DOM(第三方插件/原生js)注册事件,阻止了事件冒泡,则会导致react的相应事件无法执行,因为无法冒泡到document上,故所有的同类型事件都不生效(此条有问题,与我实际不符)
- 个人认为:真实DOM事件处理函数,与挂到document上的虚拟DOM的事件处理函数互不影响。
- 个人猜测(经过个人的多项测试):在document上,比如对于click事件:假设我们对它绑定了真实的click事件,而后虚拟DOM上的子元素也绑定了click,document就会生成个新的click事件处理函数,内部顺序执行虚拟DOM上冒泡的事件处理函数,最后执行我们为document自身绑定的click事件处理函数。得看源码去求证。
- 如果给真实的DOM(第三方插件/原生js)注册事件,事件会先于React事件运行(因为react是给document注册,冒泡到document,都已经在html元素之后了)。但前提是在ReactDOM.render前注册的真实dom事件
- 给react内置的dom组件注册的事件,若在其处理函数中阻止冒泡(e.stopPropagation(),并不会阻止真实DOM的事件,但会阻止虚拟DOM树上的事件冒泡。因为它本质是阻止在虚拟DOM中的事件冒泡
- 但是可以通过e.nativeEvent.stopImmediatePropagation(), 阻止document上剩余事件的执行
- 后来我发现给真实DOM(非document)注册事件,它的处理函数运行顺序不一定早于react事件处理函数,主要看它在ReactDOM.render之前还是之后声明的,同样的会被e.nativeEvent.stopImmediatePropagation()所影响
- 任意调用一种stopPropagation(无论有无nativeEvent),document(真实dom)上注册的同类型事件都不会触发。不论是在ReactDOM.render的前面还是后面,亲测有效
- 在事件处理程序中,不要异步的使用事件对象,因为会被重用。非要使用则可在异步外面书写e.persist(),就是让它持久化