博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React事件机制
阅读量:6230 次
发布时间:2019-06-21

本文共 3008 字,大约阅读时间需要 10 分钟。

  最近在阅读一书中,发现了之前使用React中并没有注意到的React事件与浏览器原生事件之间的区别,鉴于好久已经没有写东西了,就想写一下关于React事件的文章。

  首先我们举个例子,如果我们需要实现一个组件,这个组件点击按钮会显示一个二维码,点击二维码之外的区域可以隐藏二维码,但是点击二维码本身却不会关闭,代码如下:

//代码来源于《深入React技术栈》2.1.4节class QrCode extends Component {  constructor(props) {    super(props);    this.handleClick = this.handleClick.bind(this);    this.handleClickQr = this.handleClickQr.bind(this);    this.state = {      active: false,    };  }    componentDidMount() {    document.body.addEventListener('click', e => {      this.setState({        active: false,      });    });  }  componentWillUnmount() {    document.body.removeEventListener('click');  }    handleClick() {    this.setState({      active: !this.state.active,    });  }    handleClickQr(e) {    e.stopPropagation();  }  render() {    return (      
qr
); }}

  上面代码从感官上感觉确实可以实现要求的组件,但事实上我们运行上述代码可以发现,点击二维码本身也会导致二维码的隐藏,现在就有意思了,我们来仔细分析一下。

  其实React事件并没有原生的绑定在真实的DOM上,而是使用了行为委托方式实现事件机制。
  DOM事件机制

  如上图所示,在JavaScript中,事件的触发实质上是要经过三个阶段:事件捕获、目标对象本身的事件处理和事件冒泡,假设在div中触发了click事件,实际上首先经历捕获阶段会由父级元素将事件一直传递到事件发生的元素,执行完目标事件本身的处理事件后,然后经历冒泡阶段,将事件从子元素向父元素冒泡。正因为事件在DOM的传递经历这样一个过程,从而为行为委托提供了可能。通俗地讲,行为委托的实质就是将子元素事件的处理委托给父级元素处理。React会将所有的事件都绑定在最外层(document),使用统一的事件监听,并在冒泡阶段处理事件,当挂载或者卸载组件时,只需要在通过的在统一的事件监听位置增加或者删除对象,因此可以提高效率。

  并且React并没有使用原生的浏览器事件,而是在基于Virtual DOM的基础上实现了合成事件(SyntheticEvent),事件处理程序接收到的是SyntheticEvent的实例。SyntheticEvent完全符合W3C的标准,因此在事件层次上具有浏览器兼容性,与原生的浏览器事件一样拥有同样的接口,可以通过stopPropagation()preventDefault()相应的中断。如果需要访问当原生的事件对象,可以通过引用nativeEvent获得。
  此处输入图片的描述
  上图为大致的React事件机制的流程图,React中的事件机制分为两个阶段:事件注册和事件触发:

  1. 事件注册  

      React在组件加载(mount)和更新(update)时,其中的ReactDOMComponent会对传入的事件属性进行处理,对相关事件进行注册和存储。document中注册的事件不处理具体的事件,仅对事件进行分发。ReactBrowserEventEmitter作为事件注册入口,担负着事件注册和事件触发。注册事件的回调函数由EventPluginHub来统一管理,根据事件的类型(type)和组件标识(_rootNodeID)为key唯一标识事件并进行存储。

  2. 事件执行

      事件执行时,document上绑定事件ReactEventListener.dispatchEvent会对事件进行分发,根据之前存储的类型(type)和组件标识(_rootNodeID)找到触发事件的组件。ReactEventEmitter利用EventPluginHub中注入(inject)的plugins(例如:SimpleEventPluginEnterLeaveEventPlugin)会将原生的DOM事件转化成合成的事件,然后批量执行存储的回调函,回调函数的执行分为两步,第一步是将所有的合成事件放到事件队列里面,第二步是逐个执行。需要注意的是,浏览器原生会为每个事件的每个listener创建一个事件对象,可以从这个事件对象获取到事件的引用。这会造成高额的内存分配,React在启动时就会为每种对象分配内存池,用到某一个事件对象时就可以从这个内存池进行复用,节省内存。

  再回到我们刚开始的问题,现在看起来就很没有很费解了,之所以会出现上面的问题是因为我们混用了React的事件机制和DOM原生的事件机制,认为通过:

handleClickQr(e) {    e.stopPropagation();}

就能阻止原生的事件传播,其实在事件委托的情形下是不能实现这一点的。当然解决的办法也不复杂,不要将React事件和DOM原生事件混用。

componentDidMount() {  document.body.addEventListener('click', e => {    this.setState({      active: false,    });  });   document.querySelector('.code').addEventListener('click', e => {    e.stopPropagation();  })}componentWillUnmount() {  document.body.removeEventListener('click');  document.querySelector('.qr').removeEventListener('click');}

或者通过事件原件对象中的target进行判断:

componentDidMount() {  document.body.addEventListener('click', e => {    if (e.target && e.target.matches('div.code')) {      return;    }     this.setState({      active: false,    });  });}

都可以解决异常关闭的问题。

转载地址:http://zvxna.baihongyu.com/

你可能感兴趣的文章
Mac下没有make命令解决办法
查看>>
DLL中传递STL参数
查看>>
postgresql 范围类型
查看>>
隐藏 tengine 和 tomcat 版本号
查看>>
非面试向跨域实践详解
查看>>
一个非常好看的图片选择框架LPhotoPicker,确定不来看看么
查看>>
线上压缩代码-定位错误
查看>>
一个简洁且强大的状态管理库 - iFlow
查看>>
IP地址转换函数——inet_pton inet_ntop inet_aton inet_addr inet_ntoa
查看>>
设计模式笔记---4. 装饰模式
查看>>
springmvc + mybatis + ehcache + redis 分布式架构
查看>>
爬虫学习日记(四)分析Freenium
查看>>
nginx事件模块 -- 第五篇 epoll add
查看>>
共享栈基本操作
查看>>
Java 生成 PDF 文档
查看>>
深度学习:用生成对抗网络(GAN)来恢复高分辨率(高精度)图片 (附源码,模型与数据集)...
查看>>
缓存与数据库双写,不一致问题及解决方案
查看>>
Swift基础-部分关键字说明与示例
查看>>
【云服务月刊】2018年第1期:阿里云客户服务部总经理张颖杰:用心聆听,服务见智...
查看>>
99%的Java程序员都不知道的Spring中的@Transactional注解的坑
查看>>