事件的本质

在React中,组件的事件,本质上就是组件的一个属性(函数)
按照之前react对组件的约定,由于事件本质上是一个属性,因此也需要使用小驼峰命名法。

注意:自定义组件上声明的事件,就是props里的东西,如果没有调用就不会触发!!

  1. <Tick
  2. onClick={()=>{
  3. console.log("点击了!")
  4. }}
  5. onOver={()=>{
  6. this.setState({
  7. isOver: true
  8. })
  9. }} number={3}
  10. />

点击Tick会不会生效?会不会?当然不会了!!它不会像内置组件(原生html元素)那样点击后可以自动触发

事件处理中的this

在事件处理函数中,如果没有特殊处理,this默认指向undefined

解决方案

  1. 在constructor(也可以在组件上,但写在contructor中效率更高,因为组件重新渲染一次就会导致那些东西重新生成)中使用bind更改事件处理函数的this指向,使其指向组件实例
  2. 事件处理函数写为箭头函数(可以放在组件上,也可以放在render外,字段初始化器,this绑定在实例上)

React中的事件—扩展

跟一些原生操作DOM的第三方库混用的时候,需要特别注意,React会有些“怪异”现象!
这里的事件指的是:React内置的DOM组件中的事件,这个前提非常非常重要!!
要记住:React允许其虚拟DOM树与真实DOM有差异,可通过React开发者工具查看比对

代码

  1. //App.js
  2. import React from 'react'
  3. console.log(1111);
  4. //下述"前后"代表,是在ReactDOM.render()前还是后为原生DOM注册事件
  5. document.querySelector("#root").onclick = function(e){
  6. console.log("真实的DOM事件:id为root的div被点击了");
  7. // e.stopPropagation();//前后都是:仅仅document的click被阻止了
  8. }
  9. document.addEventListener("click", function(e){
  10. console.log("真实DOM:document被点击了!");
  11. });
  12. export default function App() {
  13. //下述测试前提是,真实dom注册事件是在ReactDOM.render(---->index.js中)之前执行的
  14. console.log(44444);
  15. return (
  16. <div onClick={()=>{
  17. console.log("react: div被点击了")
  18. }}>
  19. <button onClick={e=>{
  20. console.log("react: button被点击了");
  21. console.log(e, e.nativeEvent);
  22. console.log(e.isPropagationStopped());
  23. // e.nativeEvent.stopImmediatePropagation();//前:仅document的click被阻止了
  24. //后: root的click和document的click都被阻止了
  25. // e.nativeEvent.stopPropagation();//这是没有意义的因为已经冒泡到了document了
  26. //前后都是:仅仅document的click被阻止了,其余没影响
  27. // e.stopPropagation();//前后都是:父亲div的click和document的click都被阻止了
  28. console.log(e.isPropagationStopped());
  29. }}>
  30. 按钮
  31. </button>
  32. </div>
  33. )
  34. }
  35. console.log(22222);
  1. //index.js
  2. import React from 'react';
  3. import ReactDOM from 'react-dom';
  4. import './index.css';
  5. import App from './App';
  6. console.log(3333)
  7. ReactDOM.render((
  8. <App/>
  9. ), document.getElementById('root'));
  10. console.log(5555);

原理

  1. 最终是给document(真实DOM中的document)注册事件,这是为了提升效率
  2. 几乎所有(因为有些元素的事件本身就是不会冒泡的,比如input的focus)元素的事件处理,均在document的事件中处理(原生js事件会冒泡捕获嘛,而react是直接在document统一处理,有个队列保证执行顺序)
    • 一些不冒泡的事件,是直接在元素上监听
    • 一些document上面没有的事件,直接在元素上监听
  3. 在document的事件处理中,React会根据虚拟DOM树依次完成事件处理函数的调用,最后再调用自己的事件处理函数
  4. React的事件参数,并非真实的DOM事件参数,是React合成的一个对象,它类似于真实DOM的事件参数

    • e.stopPropagation(),阻止事件在虚拟DOM树中的冒泡
    • e.nativeEvent,可以得到真实的DOM事件对象,这个就容易整骚操作了
    • 为了提高执行效率,React使用事件对象池(意味着e有可能被重用)来处理事件对象

      注意事项

  5. 如果给真实的DOM(第三方插件/原生js)注册事件,阻止了事件冒泡,则会导致react的相应事件无法执行,因为无法冒泡到document上,故所有的同类型事件都不生效(此条有问题,与我实际不符)

    1. 个人认为:真实DOM事件处理函数,与挂到document上的虚拟DOM的事件处理函数互不影响。
    2. 个人猜测(经过个人的多项测试):在document上,比如对于click事件:假设我们对它绑定了真实的click事件,而后虚拟DOM上的子元素也绑定了click,document就会生成个新的click事件处理函数,内部顺序执行虚拟DOM上冒泡的事件处理函数,最后执行我们为document自身绑定的click事件处理函数。得看源码去求证。
  6. 如果给真实的DOM(第三方插件/原生js)注册事件,事件会先于React事件运行(因为react是给document注册,冒泡到document,都已经在html元素之后了)。但前提是在ReactDOM.render前注册的真实dom事件
  7. 给react内置的dom组件注册的事件,若在其处理函数中阻止冒泡(e.stopPropagation(),并不会阻止真实DOM的事件,但会阻止虚拟DOM树上的事件冒泡。因为它本质是阻止在虚拟DOM中的事件冒泡
  8. 但是可以通过e.nativeEvent.stopImmediatePropagation(), 阻止document上剩余事件的执行
  9. 后来我发现给真实DOM(非document)注册事件,它的处理函数运行顺序不一定早于react事件处理函数,主要看它在ReactDOM.render之前还是之后声明的,同样的会被e.nativeEvent.stopImmediatePropagation()所影响
  10. 任意调用一种stopPropagation(无论有无nativeEvent),document(真实dom)上注册的同类型事件都不会触发。不论是在ReactDOM.render的前面还是后面,亲测有效
  11. 在事件处理程序中,不要异步的使用事件对象,因为会被重用。非要使用则可在异步外面书写e.persist(),就是让它持久化