React16源码: React中event事件监听绑定的源码实现

event事件监听


1 )概述

  • 在 react-dom 代码初始化的时候,去注入了平台相关的事件插件
  • 接下去在react的更新过程绑定了事件的操作,在执行到 completeWork 的时候
  • 对于 HostComponent 会一开始就先去执行了 finalizeInitialChildren 这个方法
  • 位置在 packages/react-reconciler/src/ReactFiberCompleteWork.js#L642

2 )源码

定位到 packages/react-dom/src/client/ReactDOMHostConfig.js#L212

找到 finalizeInitialChildren

export function finalizeInitialChildren(domElement: Instance,type: string,props: Props,rootContainerInstance: Container,hostContext: HostContext,
): boolean {setInitialProperties(domElement, type, props, rootContainerInstance);return shouldAutoFocusHostComponent(type, props);
}

定位到 packages/react-dom/src/client/ReactDOMComponent.js#L447

找到 setInitialProperties

export function setInitialProperties(domElement: Element,tag: string,rawProps: Object,rootContainerElement: Element | Document,
): void {const isCustomComponentTag = isCustomComponent(tag, rawProps);if (__DEV__) {validatePropertiesInDevelopment(tag, rawProps);if (isCustomComponentTag &&!didWarnShadyDOM &&(domElement: any).shadyRoot) {warning(false,'%s is using shady DOM. Using shady DOM with React can ' +'cause things to break subtly.',getCurrentFiberOwnerNameInDevOrNull() || 'A component',);didWarnShadyDOM = true;}}// TODO: Make sure that we check isMounted before firing any of these events.let props: Object;switch (tag) {case 'iframe':case 'object':trapBubbledEvent(TOP_LOAD, domElement);props = rawProps;break;case 'video':case 'audio':// Create listener for each media eventfor (let i = 0; i < mediaEventTypes.length; i++) {trapBubbledEvent(mediaEventTypes[i], domElement);}props = rawProps;break;case 'source':trapBubbledEvent(TOP_ERROR, domElement);props = rawProps;break;case 'img':case 'image':case 'link':trapBubbledEvent(TOP_ERROR, domElement);trapBubbledEvent(TOP_LOAD, domElement);props = rawProps;break;case 'form':trapBubbledEvent(TOP_RESET, domElement);trapBubbledEvent(TOP_SUBMIT, domElement);props = rawProps;break;case 'details':trapBubbledEvent(TOP_TOGGLE, domElement);props = rawProps;break;case 'input':ReactDOMInput.initWrapperState(domElement, rawProps);props = ReactDOMInput.getHostProps(domElement, rawProps);trapBubbledEvent(TOP_INVALID, domElement);// For controlled components we always need to ensure we're listening// to onChange. Even if there is no listener.ensureListeningTo(rootContainerElement, 'onChange');break;case 'option':ReactDOMOption.validateProps(domElement, rawProps);props = ReactDOMOption.getHostProps(domElement, rawProps);break;case 'select':ReactDOMSelect.initWrapperState(domElement, rawProps);props = ReactDOMSelect.getHostProps(domElement, rawProps);trapBubbledEvent(TOP_INVALID, domElement);// For controlled components we always need to ensure we're listening// to onChange. Even if there is no listener.ensureListeningTo(rootContainerElement, 'onChange');break;case 'textarea':ReactDOMTextarea.initWrapperState(domElement, rawProps);props = ReactDOMTextarea.getHostProps(domElement, rawProps);trapBubbledEvent(TOP_INVALID, domElement);// For controlled components we always need to ensure we're listening// to onChange. Even if there is no listener.ensureListeningTo(rootContainerElement, 'onChange');break;default:props = rawProps;}assertValidProps(tag, props);setInitialDOMProperties(tag,domElement,rootContainerElement,props,isCustomComponentTag,);switch (tag) {case 'input':// TODO: Make sure we check if this is still unmounted or do any clean// up necessary since we never stop tracking anymore.inputValueTracking.track((domElement: any));ReactDOMInput.postMountWrapper(domElement, rawProps, false);break;case 'textarea':// TODO: Make sure we check if this is still unmounted or do any clean// up necessary since we never stop tracking anymore.inputValueTracking.track((domElement: any));ReactDOMTextarea.postMountWrapper(domElement, rawProps);break;case 'option':ReactDOMOption.postMountWrapper(domElement, rawProps);break;case 'select':ReactDOMSelect.postMountWrapper(domElement, rawProps);break;default:if (typeof props.onClick === 'function') {// TODO: This cast may not be sound for SVG, MathML or custom elements.trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));}break;}
}
  • 对于 iframe, object, video, audio, source 这些多媒体节点的初始化绑定
  • 是通过 trapBubbledEvent 来实现的
  • 后续执行到 setInitialDOMProperties, 在这个方法内部
    function setInitialDOMProperties(tag: string,domElement: Element,rootContainerElement: Element | Document,nextProps: Object,isCustomComponentTag: boolean,
    ): void {for (const propKey in nextProps) {if (!nextProps.hasOwnProperty(propKey)) {continue;}const nextProp = nextProps[propKey];if (propKey === STYLE) {// ... 跳过很多代码} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {// ... 跳过很多代码} else if (propKey === AUTOFOCUS) {// We polyfill it separately on the client during commit.// We could have excluded it in the property list instead of// adding a special case here, but then it wouldn't be emitted// on server rendering (but we *do* want to emit it in SSR).// 注意这里, propKey 是 dom 节点内的 props的配置,如果这个配置在 registrationNameModules 这里// registrationNameModules 是通过每一个插件里面每一个 eventTypes 里面// 它对应的有 phasedRegistrationNames 的情况下,比如说 onChange, onChangeCapture, 它都是作为它的一个key而存在的// 也就是说我们如果在这个 props 上面写了 onChange onClick 这些事件相关的props的话// 就会符合这个条件的判断,符合这个条件判断之后, 它会调用一个方法叫做 ensureListeningTo} else if (registrationNameModules.hasOwnProperty(propKey)) {if (nextProp != null) {if (__DEV__ && typeof nextProp !== 'function') {warnForInvalidEventListener(propKey, nextProp);}ensureListeningTo(rootContainerElement, propKey); // rootContainerElement 是 fiberRoot 对应的 container }} else if (nextProp != null) {// ... 跳过很多代码}}
    }
    
    • 进入 ensureListeningTo
      function ensureListeningTo(rootContainerElement, registrationName) {const isDocumentOrFragment =rootContainerElement.nodeType === DOCUMENT_NODE ||rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;// 如果它是一个document或者fragment,那么它就等于 rootContainerElement// 如果它不是,它就等于它的 rootContainerElement.ownerDocument// 这个是用来最终要去把事件绑定在哪个地方的// 可以确定的是在 react 当中大部分可冒泡的事件都是通过事件代理的形式来进行一个绑定的// 也就是说,不是每一个节点都会绑定自己的事件// 因为每个节点绑定自己的事件,肯定是性能比较低下的一个操作,而且有可能会导致内存溢出这种情况const doc = isDocumentOrFragment? rootContainerElement: rootContainerElement.ownerDocument;// 调用这个方法listenTo(registrationName, doc);
      }
      
      • 进入 listenTo
        // packages/react-dom/src/events/ReactBrowserEventEmitter.js#L126
        export function listenTo(registrationName: string,mountAt: Document | Element,
        ) {// 注意这里const isListening = getListeningForDocument(mountAt);const dependencies = registrationNameDependencies[registrationName];// 遍历依赖for (let i = 0; i < dependencies.length; i++) {const dependency = dependencies[i];// 没有这些依赖,则对 dependency 进行事件监听处理if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {switch (dependency) {case TOP_SCROLL:// 这个方法监听的是 捕获阶段的事件trapCapturedEvent(TOP_SCROLL, mountAt);break;case TOP_FOCUS:case TOP_BLUR:trapCapturedEvent(TOP_FOCUS, mountAt);trapCapturedEvent(TOP_BLUR, mountAt);// We set the flag for a single dependency later in this function,// but this ensures we mark both as attached rather than just one.isListening[TOP_BLUR] = true;isListening[TOP_FOCUS] = true;break;case TOP_CANCEL:case TOP_CLOSE:if (isEventSupported(getRawEventName(dependency))) {trapCapturedEvent(dependency, mountAt);}break;case TOP_INVALID:case TOP_SUBMIT:case TOP_RESET:// We listen to them on the target DOM elements.// Some of them bubble so we don't want them to fire twice.break;// 对于其他大部分的事件处理 用冒泡处理default:// By default, listen on the top level to all non-media events.// Media events don't bubble so adding the listener wouldn't do anything.const isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1; // 注意这里排除了 mediaEventTypes,因为一开始就已经对一些 媒体事件处理了if (!isMediaEvent) {trapBubbledEvent(dependency, mountAt); // 这是对常规事件的处理 冒泡}break;}isListening[dependency] = true;}}
        }
        
        • 进入 getListeningForDocument

          const alreadyListeningTo = {};
          let reactTopListenersCounter = 0;
          // 这个属性就是用来挂载 container 节点上面去记录这个节点监听了哪些事件的
          // 用这种方式判断是因为 可能不存在这个属性,如果没有,则需要初始化属性
          const topListenersIDKey = '_reactListenersID' + ('' + Math.random()).slice(2);
          function getListeningForDocument(mountAt: any) {// In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`// directly.if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) {mountAt[topListenersIDKey] = reactTopListenersCounter++; // 这里初始化属性alreadyListeningTo[mountAt[topListenersIDKey]] = {};}// 那如果已经有了,我们就返回这个对象, 用来记录这个dom节点它是否监听了哪些事件的return alreadyListeningTo[mountAt[topListenersIDKey]];
          }
          
        • 对于 mediaEventTypes 和媒体相关的事件

          // packages/react-dom/src/events/DOMTopLevelEventTypes.js#L155
          export const mediaEventTypes = [TOP_ABORT,TOP_CAN_PLAY,TOP_CAN_PLAY_THROUGH,TOP_DURATION_CHANGE,TOP_EMPTIED,TOP_ENCRYPTED,TOP_ENDED,TOP_ERROR,TOP_LOADED_DATA,TOP_LOADED_METADATA,TOP_LOAD_START,TOP_PAUSE,TOP_PLAY,TOP_PLAYING,TOP_PROGRESS,TOP_RATE_CHANGE,TOP_SEEKED,TOP_SEEKING,TOP_STALLED,TOP_SUSPEND,TOP_TIME_UPDATE,TOP_VOLUME_CHANGE,TOP_WAITING,
          ];
          
        • 进入 trapCapturedEvent

          export function trapCapturedEvent(topLevelType: DOMTopLevelEventType,element: Document | Element,
          ) {if (!element) {return null;}// 注意这里,根据是否是 Interactive 类型的事件,调用的不同的回调,最终赋值给 dispatchconst dispatch = isInteractiveTopLevelEventType(topLevelType)? dispatchInteractiveEvent: dispatchEvent;addEventCaptureListener(element,getRawEventName(topLevelType),// Check if interactive and wrap in interactiveUpdatesdispatch.bind(null, topLevelType),);
          }
          
          • 进入 isInteractiveTopLevelEventType
            const SimpleEventPlugin: PluginModule<MouseEvent> & {isInteractiveTopLevelEventType: (topLevelType: TopLevelType) => boolean,
            } = {eventTypes: eventTypes,// 注意这里的 topLevelEventsToDispatchConfig 一开始是一个空的对象// 在调用 addEventTypeNameToConfig 时候加入的// 这个方法是检测 isInteractiveTopLevelEventType(topLevelType: TopLevelType): boolean {const config = topLevelEventsToDispatchConfig[topLevelType];return config !== undefined && config.isInteractive === true;},// ... 跳过其他
            }
            
            • 进入 addEventTypeNameToConfig
              function addEventTypeNameToConfig([topEvent, event]: EventTuple,isInteractive: boolean,
              ) {const capitalizedEvent = event[0].toUpperCase() + event.slice(1);const onEvent = 'on' + capitalizedEvent;// 注意这个数据结构const type = {phasedRegistrationNames: {bubbled: onEvent,captured: onEvent + 'Capture',},dependencies: [topEvent],isInteractive, // 注意这里的标识};eventTypes[event] = type;topLevelEventsToDispatchConfig[topEvent] = type; // 这里进行注入
              }
              
              • 关于这里的 isInteractive 标识的来源
                interactiveEventTypeNames.forEach(eventTuple => {addEventTypeNameToConfig(eventTuple, true);
                });
                nonInteractiveEventTypeNames.forEach(eventTuple => {addEventTypeNameToConfig(eventTuple, false);
                });
                
                • 其中 interactiveEventTypeNames
                  • 参考 packages/react-dom/src/events/SimpleEventPlugin.js#L59
                • 其中 nonInteractiveEventTypeNames
                  • 参考 packages/react-dom/src/events/SimpleEventPlugin.js#L95
                • 上面两个数组对应 dom 原生的事件, 它们的区别是什么呢
                  • 这些事件去调用了设置的事件回调之后,里面如果有 setState
                  • 那么创建了update去计算的 expirationTime 会有 interactive 和 nonInteractive 的区分
                  • 它们的区别在 expirationTime,interactive的会比较的小
                  • 也就是说它的优先级会比较的高,它需要优先被执行
                  • 因为它是一个用户交互相关的事件,希望是用户比如说点了一个按钮之后
                  • 立马可以得到反馈, 因为它需要被优先执行的
          • 进入 addEventCaptureListener
            // 两者区别是第三个参数,bubble 是 false, capture 是 true
            export function addEventBubbleListener(element: Document | Element,eventType: string,listener: Function,
            ): void {element.addEventListener(eventType, listener, false); 
            }// 注意这里
            export function addEventCaptureListener(element: Document | Element,eventType: string,listener: Function,
            ): void {element.addEventListener(eventType, listener, true); // 主要是绑定 dom 原生事件
            }
            
        • 同样对于 trapBubbledEvent 也同上类似,这里不再赘述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/668712.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙应用/元服务开发-窗口(Stage模型)设置悬浮窗

一、设置悬浮窗说明 悬浮窗可以在已有的任务基础上&#xff0c;创建一个始终在前台显示的窗口。即使创建悬浮窗的任务退至后台&#xff0c;悬浮窗仍然可以在前台显示。通常悬浮窗位于所有应用窗口之上&#xff1b;开发者可以创建悬浮窗&#xff0c;并对悬浮窗进行属性设置等操…

使用网关过滤器,根据业务规则实现微服务动态路由

文章目录 业务场景拦截器实现Spring Cloud Gateway介绍 业务场景 我们服务使用Spring Cloud微服务架构&#xff0c;使用Spring Cloud Gateway 作为网关&#xff0c;使用 Spring Cloud OpenFeign 作为服务间通信方式作为网关&#xff0c;主要作用是鉴权与路由转发。大多数应用场…

3d网上虚拟现实展厅让汽车零部件厂商脱颖而出

在这个信息爆炸的时代&#xff0c;如何让自己的产品在众多竞争者中脱颖而出?让我们为您揭示一个秘密武器——汽车线上3D云展示软件。 想象一下&#xff0c;一辆外观炫酷、性能卓越的红色汽车&#xff0c;通过这款3D云展示软件&#xff0c;呈现在潜在客户的眼前。那流线型的车身…

Failed at the chromedriver@2.27.2 install script.

目录 【错误描述】Failed at the chromedriver2.27.2 install script. npm install报的错误 【解决方法】 删除node_modules文件夹npm install chromedriver --chromedriver_cdnurlhttp://cdn.npm.taobao.org/dist/chromedrivernpm install 【未解决】 下载该zip包运行这个&…

【npm】安装全局包,使用时提示:不是内部或外部命令,也不是可运行的程序或批处理文件

问题 如图&#xff0c;明明安装Vue是全局包&#xff0c;但是使用时却提示&#xff1a; 解决办法 使用以下命令任意一种命令查看全局包的配置路径 npm root -g 然后将此路径&#xff08;不包括node_modules&#xff09;添加到环境变量中去&#xff0c;这里注意&#xff0c;原…

JAVA SpringBoot中使用redis的事务

1、自定义redisConfig , 如果项目中要使用redis事务&#xff0c;最好将用事务和不用事务的redis分开。 Configuration public class RedisConfig {Resourceprivate RedisProperties redisProperties;Bean("redisTemplate")public RedisTemplate<String, Object>…

前端框架学习 Vue(3)vue生命周期,钩子函数,工程化开发脚手架CLI,组件化开发,组件分类

Vue 生命周期 和生命周期的四个阶段 Vue生命周期:一个Vue实例从创建 到 销毁 的整个过程 生命周期四个阶段 :(1)创建 (2)挂载 (3)更新 (4)销毁 Vue生命周期函数(钩子函数) Vue生命周期过程中,会自动运行一些函数,被称为[生命周期钩子] ->让开发者可以在[特定阶段] 运行自…

【云原生kubernetes系列】---亲和与反亲和

1、亲和和反亲和 node的亲和性和反亲和性pod的亲和性和反亲和性 1.1node的亲和和反亲和 1.1.1ndoeSelector&#xff08;node标签亲和&#xff09; #查看node的标签 rootk8s-master1:~# kubectl get nodes --show-labels #给node节点添加标签 rootk8s-master1:~# kubectl la…

用GOGS搭建GIT服务器

GOGS官网 Gogs: A painless self-hosted Git service 进入文件所在目录 cd /usr/local/develop 解压文件 tar -xvf gogs_0.13.0_linux_amd64.tar.gz 解压之后 进入gogs 目录 cd gogs 创建几个目录 userdata 存放用户数据 log文件存放进程日志 repositories 仓库根目…

19113133262(微信同号)【主题广范|见刊快】2024年新材料与应用化学国际学术会议(ICNMAC 2024)

【主题广范|见刊快】2024年新材料与应用化学国际学术会议(ICNMAC 2024) 2024 International Conference New Materials and Applied Chemistry(ICNMAC 2024) 一、【会议简介】 会议背景&#xff1a;随着科技的飞速发展&#xff0c;新材料与应用化学领域的研究成果日益丰富。为…

MySQL进阶45讲【12】为什么你的MySQL偶尔会卡一下

1 前言 平时的工作中&#xff0c;不知道大家有没有遇到过这样的场景&#xff0c;一条SQL语句&#xff0c;正常执行的时候特别快&#xff0c;但是有时也不知道怎么回事&#xff0c;它就会变得特别慢&#xff0c;并且这样的场景很难复现&#xff0c;它不只随机&#xff0c;而且持…

事件驱动架构:使用Flask实现MinIO事件通知Webhooks

MinIO的事件通知可能一开始看起来并不激动人心&#xff0c;但一旦掌握了它们的力量&#xff0c;它们就能照亮您存储桶内的动态。事件通知是一个全面、高效的对象存储系统中的关键组件。Webhooks是我个人最喜欢的工具&#xff0c;用于与MinIO集成。它们在事件的世界中就像一把瑞…

基于NSGA-II的深度迁移学习

深度迁移学习 迁移学习是一种机器学习技术&#xff0c;它允许一个预训练的模型被用作起点&#xff0c;在此基础上进行微调以适应新的任务或数据。其核心思想是利用从一个任务中学到的知识来帮助解决另一个相关的任务&#xff0c;即使这两个任务的数据分布不完全相同。这种方法…

靶机实战bwapp亲测xxe漏洞攻击及自动化XXE注射工具分析利用

靶机实战bwapp亲测xxe漏洞攻击及自动化XXE注射工具分析利用。 1|0介绍 xxe漏洞主要针对webservice危险的引用的外部实体并且未对外部实体进行敏感字符的过滤,从而可以造成命令执行,目录遍历等.首先存在漏洞的web服务一定是存在xml传输数据的,可以在http头的content-type中查…

sql求解连续两个以上的空座位

Q&#xff1a;查找电影院所有连续可用的座位。 返回按 seat_id 升序排序 的结果表。 测试用例的生成使得两个以上的座位连续可用。 结果表格式如下所示。 A:我们首先找出所有的空座位&#xff1a;1&#xff0c;3&#xff0c;4&#xff0c;5 按照seat_id排序&#xff08;上面已…

Android studio使用svg矢量图

https://www.iconfont.cn/ https://www.jyshare.com/more/svgeditor/ https://editor.method.ac/ https://inloop.github.io/svg2android/ Pattern Monster - SVG 图案生成器 Android studio使用svg矢量图自适应不同的分辨率&#xff0c; svg矢量图绘制以及转换为And…

前端小案例——动态导航栏文字(HTML + CSS, 附源码)

一、前言 实现功能: 这案例是一个具有动态效果的导航栏。导航栏的样式设置了一个灰色的背景&#xff0c;并使用flex布局在水平方向上平均分配了四个选项。每个选项都是一个li元素&#xff0c;包含一个文本和一个横向的下划线。 当鼠标悬停在选项上时&#xff0c;选项的文本颜色…

IntelliJ IDEA - 5 个相见恨晚的 IDEA 使用技巧,能让你的代码飞起来!

日常开发中&#xff0c;相信广大 Java 开发者都使用过 IntelliJ IDEA 作为开发工具&#xff0c;IntelliJ IDEA 是一款优秀的 Java 集成开发环境&#xff0c;它提供了许多强大的功能和快捷键&#xff0c;可以帮助开发者提高编码效率和质量。除了一些常见的技巧&#xff0c;如自动…

LLM - Qwen-72B LoRA 训练与推理实战

目录 一.引言 二.模型简介 1.Qwen-Model 总览 2.Qwen-Chat-72B - PreTrain - Tokenizer - Base Line - SFT / RLHF 3.Qwen-72 模型架构 - Config.json - c_attn/c_proj - Attention Forward - ROPE - Qwen MLP - Qwen Block 三.QLoRA 与 Infer 实战 1.SFT 指…

连州直播大赛:培训新主播,用直播连接农产品与市场

摘要:在广东省连州市,一场别开生面的直播大赛不仅为当地特色农产品打开了销售新渠道,更重要的是,它通过赛中学习的机制,为参与者提供了宝贵的线上营销培训,培育了一批具备电商直播技能的新型农商人才,开启了电子商务促进连州农业经济发展的新篇章。 正文: 广东省连州市最近举…