React事件杂记及源码分析
前提
最近通过阅读React官方文档的事件模块,发现了其主要提到了以下三个点
- 调用方法时需要手动绑定this
- React事件是一种合成事件SyntheticEvent,什么是合成事件?
- 事件属性会在事件调用后被回收,即不能异步访问
- 事件机制的源码分析
1).注册阶段源码分析
2).触发阶段源码分析
3).总结相关流程
带着问题,通过查询资料和源码来探寻~
1.调用方法时需要手动绑定this
先从一段官方代码看起:
代码中的注释提到了一句话:
This binding is necessary to make `this` work in the callback
this的绑定是必须的,其实这一块是比较容易理解的, 因为这并不是React的一个特殊点, 而是Javascript这门语言的特性。
可以看到,调用的是this.handleClick函数,handleClick函数里面又读取到了this属性,但是该函数的调用位置又是在render函数里面,render返回的是一个JSX,最后经过babel编译成调用React.createElement函数,
在这之前,我们掌握的是this永远指向的是最后调用它的对象,经过这样的一个转换, 实际上this最后指向的是undeined了, 那么调用handleClick函数自然会报错。
当然,如果你不在函数里面使用this的话,通常会没事,但并不建议这么做。
关于this的指向与function的原理,推荐阅读 how functions work in JavaScript
既然知道了是因为this的指向原因而采用绑定的做法,那当然可以用箭头函数来解决了,箭头函数中的this是在定义函数的时候绑定,也就是说this是继承自父执行上下文,如下:
这样this也能达到我们的预期效果
React事件是一种合成事件SyntheticEvent,什么是合成事件?
先从官方上的一段话看起,他的意思是合成事件是React根据W3C标准定义的,无需担心浏览器之间的差异
Here, e is a synthetic event. React defines these synthetic events according to the W3C spec, so you don’t need to worry about cross-browser compatibility
这样看起来React的合成事件只是兼容浏览器? 答案当然是远远不止啦!
在探寻其优点之前,我们先看一下其是怎样的一个机制。
React的事件机制其实网上有很多同学都分析过了, 他并没有将事件注册在对应的元素或者组件上面,而是通过委托的方式,将所有的事件都注册到了document对象上,并统一调用一个dispatch回调函数,其流程图如下
我们也可以从一个实际的简单例子看看:
我们把回调函数绑定到了button上,但是在事件上却没有看到button元素, 但是却有document,并且可以看到他的回调函数就是dispatchInteractiveEvent
最后触发事件的回调函数时,在原生的DOM会传入一个事件属性event,但是因为React将 所有事件委托给document处理, 那么这个event就和我们想要的不一样,如target指向的是document,于是React就有了自己的一个合成事件,通过一个叫SyntheticEvent的基类来生成所需要的事件属性,并传入回调函数作为方法。
说到底,React就是把所有事件委托给document处理, 那么这样做有什么好处:
- 可以统一在组件挂载和卸载时做处理
- 只需要注册一个事件即可,节省内存开销
- 可以手动控制事件流程,特别是对state的batch处理(参考React系列的setState)
事件属性会在事件调用后被回收,即不能异步访问
老规矩,先上一段代码:
可以看到在setTimeout函数中,访问事件属性是null。这是为啥?
其实这也是合成事件的一个优化手段。 React会在事件调用完成后清理掉属性,否则每点击一次就生成一个事件,那么内存的开销会越来越大,具体的代码可以在后面的源码分析中看到:
当然了, React也可以手动设置不回收,如下:
If you want to access the event properties in an asynchronous way, you should call event.persist() on the event
我们可以通过调用event,persist来设置不回收。
事件机制的源码分析
注册阶段
首先在某一个任务单元fiber调用compeleteWork函数时, React会判断其是否具有事件属性, 如果有则调用ensureListeningTo函数
ensureListeningTo函数主要是获取到document对象, 并调用listenTo函数
listerTo函数 主要是通过调用trapBubbledEvent或者trapCapturedEvent将事件放在document事件上监听
trapBubbledEvent主要是监听事件, 但也可以看出, 所有事件最后触发的都是注册在document上的dispatch函数
调用阶段
dispatch函数, 主要是获取实际触发的元素以及对应的fiber, 最后调用batchedUpdates函数, batchedUpdates函数里面的逻辑主要是关于setState的,这里主要是看事件机制, 只要知道最后调用的是handleTopLevel(bookkeeping)就好
handleTopLevel函数主要是拿到需要触发事件的相关fiber, 并调用runExtractedEventsInBatch函数
extractEvents函数是一个生成React事件的函数,React事件是通过继承一个通用类SyntheticEvent生成的,如一个鼠标事件的生成
React事件内部做了优化, 只要生成过SyntheticMouseEvent类, 就会再释放事件的时候将这个类存储起来,在下一个事件触发时可以直接使用
React生成事件后, 会调用accumulateTwoPhaseDispatches(event)函数,该函数一直追溯下去, 最后会调用traverseTwoPhase函数,
traverseTwoPhase函数主要是获取祖先组件的fiber, 并进行捕获和冒泡的阶段处理
accumulateDirectionalDispatches函数相对简单, 就是把fiber上对应的事件函数赋值给evnet的_dispatchListeners属性
React事件获取完成后, 回到runExtractedEventsInBatch函数继续调用runEventsInBatch(events, false); 函数的中间作了一系列的处理, 但最后执行的是executeDispatchesAndRelease函数
executeDispatchesAndRelease函数会在执行完事件后判断用户是否有设置不销毁事件, 如果没有, 则销毁事件并保存事件类, 一个事件类实例一次并重复使用, 这也是为什么官方提到事件属性只能在当前循环中读到
继续往下走, 最后执行的函数是invokeGuardedCallbackDev, 该函数通过注册一个自定义的元素<react>和自定义的事件, 并触发它来达到执行回调函数的功能
最后, 总结下相关的流程:
- 通过Fiber中的属性, 将事件统一委托 注册到document上,并为document注册相应的事件回调函数 dispatch函数。
- 先获取实际触发元素对应的fiber.
- 生成相应的React事件属性event,将对应的回调函数赋值给event._dispatchListeners, 将fiber赋值给event._dispatchInstances
- 通过fiber向上遍历, 找到所有的祖先fiber, 并按原生事件的机制先捕获后冒泡的执行事件
- 注册一个react节点, 为其注册一个监听事件并触发来执行事件回调函数
- 最后,根据用户的设置, 决定是否释放事件。
React事件杂记及源码分析的更多相关文章
- Android View事件分发-从源码分析
View事件分发-从源码分析 学习自 <Android开发艺术探索> https://blog.csdn.net/qian520ao/article/details/78555397?lo ...
- Android事件分发机制源码分析
Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...
- Cocos2d-X3.0 刨根问底(七)----- 事件机制Event源码分析
这一章,我们来分析Cocos2d-x 事件机制相关的源码, 根据Cocos2d-x的工程目录,我们可以找到所有关于事件的源码都存在放在下图所示的目录中. 从这个event_dispatcher目录中的 ...
- Android查缺补漏(View篇)--事件分发机制源码分析
在上一篇博文中分析了事件分发的流程及规则,本篇会从源码的角度更进一步理解事件分发机制的原理,如果对事件分发规则还不太清楚的童鞋,建议先看一下上一篇博文 <Android查缺补漏(View篇)-- ...
- (转)spring boot实战(第三篇)事件监听源码分析
原文:http://blog.csdn.net/liaokailin/article/details/48194777 监听源码分析 首先是我们自定义的main方法: package com.lkl. ...
- Qt事件分发机制源码分析之QApplication对象构建过程
我们在新建一个Qt GUI项目时,main函数里会生成类似下面的代码: int main(int argc, char *argv[]) { QApplication application(argc ...
- Android事件传递机制详解及最新源码分析——View篇
摘要: 版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于安卓事件传递机制相信绝大部分开发者都听说过或者了解过,也是面试中最常问的问题之一.但是真正能从源码角度理解具体事件传递流程的相信并不多, ...
- Android事件传递机制详解及最新源码分析——ViewGroup篇
版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...
- Android事件传递机制详解及最新源码分析——Activity篇
版权声明:本文出自汪磊的博客,转载请务必注明出处. 在前两篇我们共同探讨了事件传递机制<View篇>与<ViewGroup篇>,我们知道View触摸事件是ViewGroup传递 ...
随机推荐
- Vue(三十三)国际化解决方案
摘自:https://blog.csdn.net/qq_41485414/article/details/81093999 (1)第一种方式:中英文两套页面 优点:技术含量最低 缺点:占内存,响应慢, ...
- 201771010126 王燕《面向对象程序设计(Java)》第九周学习总结
实验九 异常.断言与日志 实验时间 2018-10-25 1.实验目的与要求 (1) 掌握java异常处理技术: 异常积极处理方法:使用try子句捕获异常 异常小计处理方法:抛出throw异常类 (2 ...
- 邮件服务器 postfix
背景介绍 邮件服务器普遍需要一个主机名来使得mail from 以"账号@主机名"方式显示.由于外网上垃圾邮件太多,现在已不使用ip发邮件,很多网络供应商都会对来源不明的邮件进行限 ...
- prometheus — 基于文件的服务发现
基于文件的服务发现方式不需要依赖其他平台与第三方服务,用户只需将要新的target信息以yaml或json文件格式添加到target文件中 ,prometheus会定期从指定文件中读取target信息 ...
- Android Gradle Task
Tasks runnable from root project ------------------------------------------------------------ Androi ...
- Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作
写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...
- Golang Go Go Go part3:数据类型及操作
五.Go 基本类型 1.基本类型种类 布尔值: bool 长度 1字节 取值范围 true, false注意事项:不可用数字代表 true 或 false 整型: int/uint 根据运行平台可能为 ...
- SQLite异常 qAdmin: Cannot perform this operation on a closed dataset.【申明:来源于网络】
SQLite异常 qAdmin: Cannot perform this operation on a closed dataset. 当使用 SQLite administrator,打开SQLit ...
- Springboot Selenide UI 自动化测试
标题中的Selenide 并没有拼错,确实不是selenium Selenium做UI自动化,可以参考我其他的blog: Selenium做自动化最好要封装起来,否则对于元素的等待,页面的加载会使得自 ...
- 【RL-TCPnet网络教程】第21章 RL-TCPnet之高效的事件触发框架
第21章 RL-TCPnet之高效的事件触发框架 本章节为大家讲解高效的事件触发框架实现方法,BSD Socket编程和后面章节要讲解到的FTP.TFTP和HTTP等都非常适合使用这种方式 ...