React16源码: React中event事件对象的创建过程源码实现

event 对象


1 ) 概述

  • 在生产事件对象的过程当中,要去调用每一个 possiblePlugin.extractEvents 方法
  • 现在单独看下这里面的细节过程,即如何去生产这个事件对象的过程

2 )源码

定位到 packages/events/EventPluginHub.js#L172

function extractEvents(topLevelType: TopLevelType,targetInst: null | Fiber,nativeEvent: AnyNativeEvent,nativeEventTarget: EventTarget,
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {let events = null;for (let i = 0; i < plugins.length; i++) {// Not every plugin in the ordering may be loaded at runtime.const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];if (possiblePlugin) {// 这里要去调用 每个 plugin 的 extractEvents 方法const extractedEvents = possiblePlugin.extractEvents(topLevelType,targetInst,nativeEvent,nativeEventTarget,);if (extractedEvents) {events = accumulateInto(events, extractedEvents);}}}return events;
}

注意这里的 possiblePlugin.extractEvents 我们专门专注下 changeEvent 的这个方法
定位到 packages/react-dom/src/events/ChangeEventPlugin.js#L263

const ChangeEventPlugin = {eventTypes: eventTypes,_isInputEventSupported: isInputEventSupported,// 注意这里extractEvents: function(topLevelType,targetInst,nativeEvent,nativeEventTarget,) {// 在这个方法里面,拿到了 targetNode// 因为这边传进来的呢是一个发布对象, 所以要通过这种方法拿到它的 nodeconst targetNode = targetInst ? getNodeFromInstance(targetInst) : window;// 然后,要经过一系列的判断,主要去赋值了不同的 getTargetInstFunclet getTargetInstFunc, handleEventFunc;// 如果有返回这个instance,那么我们就可以去创建这个event了,这是什么意思呢?// 就是说我们这个 event plugin, 在所有的事件触发的过程当中,这个plugin都会被循环调用的// 它是没有通过事件名称来调用不同的plugin这么一个设置的// 所以这个判断是要放在每个pluggin里面自己去做。就是说根据这次触发的具体事件是什么?// 来判断我们要不要为它创建一个event,因为每个plugin在每次事件触发都会被调用// 如果我们都生成事件,那么明显是不对的,肯定要对自己这个 plugin 关心的事件来去为它生成这个事件if (shouldUseChangeEvent(targetNode)) {getTargetInstFunc = getTargetInstForChangeEvent;} else if (isTextInputElement(targetNode)) {if (isInputEventSupported) {getTargetInstFunc = getTargetInstForInputOrChangeEvent;} else {// polyfill 的这些先忽略getTargetInstFunc = getTargetInstForInputEventPolyfill;handleEventFunc = handleEventsForInputEventPolyfill;}} else if (shouldUseClickEvent(targetNode)) {getTargetInstFunc = getTargetInstForClickEvent;}// 基于类型,得到了最终的处理函数if (getTargetInstFunc) {const inst = getTargetInstFunc(topLevelType, targetInst);if (inst) {// 创建 eventconst event = createAndAccumulateChangeEvent(inst,nativeEvent,nativeEventTarget,);return event;}}if (handleEventFunc) {handleEventFunc(topLevelType, targetNode, targetInst);}// When blurring, set the value attribute for number inputsif (topLevelType === TOP_BLUR) {handleControlledInputBlur(targetNode);}},
};
  • 进入 shouldUseChangeEvent

    function shouldUseChangeEvent(elem) {const nodeName = elem.nodeName && elem.nodeName.toLowerCase();return (nodeName === 'select' || (nodeName === 'input' && elem.type === 'file'));
    }
    
    • 这个判断其实就是主要来判断一下我们这个节点上面是否有changeevent
    • 并且是否应该用 change event 来进行一个触发
    • 因为react当中的 onchange 事件,其实它是封装了各种不同的事件的
    • 比如说对于像我们输入文本的 input type=‘text’ 的一个情况
    • 正常来讲,应该绑定的是input事件,而不是change事件
    • 因为change事件在有些浏览器里面要等到这个输入框,blur的时候,才会真正触发这个change事件
    • 对于input事件是我们每次有输入变化的时候,都会触发的这个事件
    • 所以对于 select 还有 input type=‘file’, 它们的change是非常明显的,就是等到它们有内容变化的时候就会触发
    • 因为file是我们选择了一个文件之后,它就会触发change事件
    • 而select我们选择了某一个 option 之后,它也会触发这个change事件
    • 所以对于这种节点,我们可以直接使用onchange来进行一个绑定
    • 这时候, getTargetInstFunc = getTargetInstForChangeEvent; 进入 getTargetInstForChangeEvent
      function getTargetInstForChangeEvent(topLevelType, targetInst) {// TOP_CHANGE 就是 changeif (topLevelType === TOP_CHANGE) {return targetInst;}
      }// 注意 另外的文件中
      // packages/react-dom/src/events/DOMTopLevelEventTypes.js#L41
      export const TOP_CHANGE = unsafeCastStringToDOMTopLevelType('change');// packages/events/TopLevelEventTypes.js#L27
      export function unsafeCastStringToDOMTopLevelType(topLevelType: string,
      ): DOMTopLevelEventType {return topLevelType;
      }
      
  • 进入 isTextInputElement

    const supportedInputTypes: {[key: string]: true | void} = {color: true,date: true,datetime: true,'datetime-local': true,email: true,month: true,number: true,password: true,range: true,search: true,tel: true,text: true,time: true,url: true,week: true,
    };function isTextInputElement(elem: ?HTMLElement): boolean {// 获取 nodeNameconst nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();// 判断在 input 的时候,是否符合支持的type类型if (nodeName === 'input') {return !!supportedInputTypes[((elem: any): HTMLInputElement).type];}if (nodeName === 'textarea') {return true;}return false;
    }
    
    • 这里就是一个 boolean 类型的函数进行类型判断的
  • 进入 getTargetInstForInputOrChangeEvent

    // packages/react-dom/src/events/ChangeEventPlugin.js#L229
    function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {if (topLevelType === TOP_INPUT || topLevelType === TOP_CHANGE) {return getInstIfValueChanged(targetInst);}
    }// packages/react-dom/src/events/ChangeEventPlugin.js#L106
    function getInstIfValueChanged(targetInst) {const targetNode = getNodeFromInstance(targetInst);if (inputValueTracking.updateValueIfChanged(targetNode)) {return targetInst;}
    }// packages/react-dom/src/client/ReactDOMComponentTree.js#L69
    export function getNodeFromInstance(inst) {if (inst.tag === HostComponent || inst.tag === HostText) {// In Fiber this, is just the state node right now. We assume it will be// a host component or host text.return inst.stateNode;}// Without this first invariant, passing a non-DOM-component triggers the next// invariant for a missing parent, which is super confusing.invariant(false, 'getNodeFromInstance: Invalid argument.');
    }
    
  • 进入 shouldUseClickEvent

    /*** SECTION: handle `click` event*/// checkbox 和 radio 的特殊处理
    function shouldUseClickEvent(elem) {// Use the `click` event to detect changes to checkbox and radio inputs.// This approach works across all browsers, whereas `change` does not fire// until `blur` in IE8.const nodeName = elem.nodeName;return (nodeName &&nodeName.toLowerCase() === 'input' &&(elem.type === 'checkbox' || elem.type === 'radio'));
    }
    
  • 进入 getTargetInstForClickEvent

    function getTargetInstForClickEvent(topLevelType, targetInst) {if (topLevelType === TOP_CLICK) {return getInstIfValueChanged(targetInst);}
    }
    
  • 进入 createAndAccumulateChangeEvent 创建事件对象

    function createAndAccumulateChangeEvent(inst, nativeEvent, target) {const event = SyntheticEvent.getPooled(eventTypes.change,inst,nativeEvent,target,);event.type = 'change';// Flag this event loop as needing state restore.enqueueStateRestore(target);accumulateTwoPhaseDispatches(event);return event;
    }
    
    • 这个函数就是具体生成这个事件的一个过程
      • 可以看到这个事件,接收了一个 inst,然后传入了 nativeEvent,并且再传入 target
      • 然后,通过 SyntheticEvent.getPooled,就是说在react当中所有的事件对象是通过一个 pool 来进行一个存储的
      • 比如说我们为所有的event创建了十个event对象
      • 每一次有新的一个event进来的时候,从这个pool里面拿出一个设置一些事件以及对应的一些值之后
      • 去触发每一个事件的监听方法,然后去使用这个 event 对象
      • 这个 event 对象使用完了之后,又会归还到这个 pool 里面
      • 也就是一个 能够减少 对象声明 以及 对象回收 的一个性能开销
      • 然后拿到了这个 event 之后,给它设置了 type 是 change
      • 之后执行两个函数 enqueueStateRestoreaccumulateTwoPhaseDispatches
    • 进入 SyntheticEvent.getPooled
      // packages/events/SyntheticEvent.js#L335
      function addEventPoolingTo(EventConstructor) {EventConstructor.eventPool = [];EventConstructor.getPooled = getPooledEvent; // 注意这里EventConstructor.release = releasePooledEvent;
      }
      
      • 进入 getPooledEvent
        // packages/events/SyntheticEvent.js#L300
        function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {const EventConstructor = this;// 存在pollif (EventConstructor.eventPool.length) {const instance = EventConstructor.eventPool.pop();EventConstructor.call(instance,dispatchConfig,targetInst,nativeEvent,nativeInst,);return instance;}// pool 里面没有,则创建一个新的return new EventConstructor(dispatchConfig,targetInst,nativeEvent,nativeInst,);
        }
        
        • 关于 EventConstructor
          • 首先在 packages/events/SyntheticEvent.js#L62 中的 SyntheticEvent 构造方法
          • 要理解这个过程, 首先在这个js里面先声明了一个叫做 SyntheticEvent 这么一个方法
          • 这个方法它是一个constructor 方法, 在这个方法里面去声明事件相关的各种属性
          • 重点关注它的事件的一个触发的过程以及生产的过程,所以只关心它的 pool 的处理过程
        • 这里的 this 就是 SyntheticEvent
          • 在这个构造方法的原型链上也有一大堆的东西,对事件对象进行一个封装和扩展
        • 注意这里的 EventConstructor.callnew EventConstructor 都达到同一个目的
    • 进入 enqueueStateRestore
      export function enqueueStateRestore(target: EventTarget): void {// 判断了这个 restoreTarget 公共变量是否存在if (restoreTarget) {if (restoreQueue) {restoreQueue.push(target);} else {restoreQueue = [target];}// restoreTarget 不存在,则对其进行赋值} else {restoreTarget = target;}
      }
      
      • 作用是处理,如果setState之后,这个 state 对应的 input 的 value 是不一样的
      • 要把这个值进行一个回滚
    • 进入 accumulateTwoPhaseDispatches 这个方法才是真正要去从每个节点上面去获取它的 listener 的一个过程
      // packages/events/EventPropagators.js#L115
      export function accumulateTwoPhaseDispatches(events) {// 其实就是对 events 这个数组里面,它的每一个节点去调用这个方法forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
      }function accumulateTwoPhaseDispatchesSingle(event) {// 存在 phasedRegistrationNames 则调用 traverseTwoPhaseif (event && event.dispatchConfig.phasedRegistrationNames) {traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);}
      }// 在 event 对象上 插入 listener 的过程
      function accumulateDirectionalDispatches(inst, phase, event) {// 忽略if (__DEV__) {warningWithoutStack(inst, 'Dispatching inst must not be null');}// 获取 listenerconst listener = listenerAtPhase(inst, event, phase);if (listener) {// 注意这里 event._dispatchListeners 和 下面的 event._dispatchInstances 保持两者一一对应的关系event._dispatchListeners = accumulateInto(event._dispatchListeners,listener,);event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);}
      }function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {const registrationName =event.dispatchConfig.phasedRegistrationNames[propagationPhase];return getListener(inst, registrationName);
      }// packages/events/EventPluginHub.js#L126
      export function getListener(inst: Fiber, registrationName: string) {let listener;// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not// live here; needs to be moved to a better place soonconst stateNode = inst.stateNode;if (!stateNode) {// Work in progress (ex: onload events in incremental mode).return null;}const props = getFiberCurrentPropsFromNode(stateNode); // 从 dom tree上获取 propsif (!props) {// Work in progress.return null;}listener = props[registrationName];if (shouldPreventMouseEvent(registrationName, inst.type, props)) {return null;}invariant(!listener || typeof listener === 'function','Expected `%s` listener to be a function, instead got a value of `%s` type.',registrationName,typeof listener,);return listener;
      }// packages/shared/ReactTreeTraversal.js#L86
      export function traverseTwoPhase(inst, fn, arg) {const path = [];// 找到所有上层节点,并存入 pathwhile (inst) {path.push(inst);inst = getParent(inst);}// 下面是核心,执行两个阶段的回调,捕获和冒泡let i;for (i = path.length; i-- > 0; ) { // 注意这个 i 的顺序// path[i]:节点, arg:eventfn(path[i], 'captured', arg); // captured 是从 window 向下触发的}for (i = 0; i < path.length; i++) { // 注意这个 i 的顺序fn(path[i], 'bubbled', arg); // bubbled 是从下向 window 方向的}// 基于上面两个 循环// 这样的话,就不需要在 event 对象上面单独维护 capture 的这个事件的它的一个数组// 还有 bubble 的事件的一个速度,只需要放在同一个数组里面,然后按照这个数组的顺序去触发就可以了
      }
      // packages/shared/ReactTreeTraversal.js#L10
      function getParent(inst) {do {inst = inst.return;// TODO: If this is a HostRoot we might want to bail out.// That is depending on if we want nested subtrees (layers) to bubble// events to their parent. We could also go through parentNode on the// host node but that wouldn't work for React Native and doesn't let us// do the portal feature.} while (inst && inst.tag !== HostComponent);if (inst) {return inst; // 返回的 inst 是一个 HostComponent}return null;
      }
      
  • 以上,生产 event 对象,然后去挂载它的事件,这个过程是非常的复杂的

  • react 团队把整个事件系统去重新抽象的这么一个过程,而且设计的超级复杂

  • 这一套东西只是非常适合react,在其他框架要使用这类event库,会有很大的成本

  • 目前为止,通过 ChangeEventPlugin 来了解了整个 event 对象的处理过程

  • 后续其他的类似事件的处理逻辑到后面都是一样的

  • 但每一个 plugin 或多或少有一些自己的一些区别,这里不再赘述

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

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

相关文章

Spring事件之注解@EventListener讲解

文章目录 1 注解EventListener1.1 示例Demo1.1.1 简单例子1.1.2 解耦1.1.3 Spring事件 1.2 深入EventListener1.2.1 debug调试1.2.2 问题一&#xff1a; Spring是怎么知道要去触发这个方法1.2.3 问题二&#xff1a;ApplicationListenerMethodAdapter1.2.4 问题三&#xff1a;Si…

Vue学习笔记:计算属性

计算属性 入门进阶二次进阶三次进阶四次进阶结界五次进阶六次进阶七次进阶八次进阶九次进阶终章彩蛋 入门 Vue.js中&#xff0c;计算属性示例&#xff1a; export default {data() {return {firstName: John,lastName: Doe};},computed: {// 计算属性&#xff1a;全名fullNam…

(12)喝汽水

文章目录 每日一言题目解题思路一代码 解题思路二代码 结语 每日一言 长风沛雨&#xff0c;艳阳明月。田野被喜悦铺满&#xff0c;天地间充满着生的豪情。 题目 已知1瓶汽水1元&#xff0c;2个空瓶可以换一瓶汽水&#xff0c;输入整数n&#xff08;n>0&#xff09;&#x…

Maven工程的配置及使用

一、Maven章节 Maven 是 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具 1.1、maven的作用 1&#xff09;依赖管理&#xff1a; 方便快捷的管理项目依赖的资源包&#xff08;jar包&#xff09;避免版本冲突 2&#xff09;统一项目结构&…

机器学习算法之逻辑回归算法(Logistic Regression)

逻辑回归算法是一种用于分类问题的经典机器学习算法。虽然它的名字中带有“回归”,但实际上逻辑回归用于解决分类问题,特别是二分类问题。本篇博文将详细介绍逻辑回归算法的工作原理、应用领域以及Python示例。 算法背景 逻辑回归起源于20世纪初,用于分析生存率数据。随后…

Android studio打开md无法显示md渲染问题

Where is Android Studio Markdown support plugin preview preference? - Stack Overflow android studio开发无法选择markdown渲染功能的问题 原因是java runtime出了问题 搜索下面功能 Choose Boot Java Runtime for the IDE 选择带JCEF的 可以选最新的java版本 重启之…

Jvm FullGC 如何排查?

使用场景 我们在使用系统时&#xff0c;有时请求和响应会变得特别慢&#xff0c;系统也变得很卡。 有可能是FullGC的问题&#xff0c;可以逐步地进行排查。 使用jps和top确定进程号pid jps可以列出正在运行的jvm进程&#xff0c;并显示jvm执行主类名称( main()函数所在的类…

浙政钉(专有钉钉)

专有钉钉是浙政钉的测试版本&#xff0c;可在正式发布之前进行业务开发。 专有钉钉 原名政务钉钉 是高安全、强管控、灵活开放的面向大型组织专有独享的协同办公平台。支持专有云、混合云等多种方式灵活部署&#xff0c;以满足客户特定场景所需为目标&#xff0c;最大化以“平…

window 镜像---负载篇

前提&#xff1a;需要修改window的powershell执行脚本的策略 步骤&#xff1a;以管理员身份打开powershell&#xff0c;执行 Get-ExecutionPolicy查看当前执行策略&#xff0c;若返回值是Restricted&#xff0c;需执行Set-ExecutionPolicy RemoteSigned powershell 版本信息&am…

.NET Core已经开源好几年了, 为什么不像JVM那样很多人研究和调优其GC算法?

.NET Core的开源确实是微软近年来重要的一步&#xff0c;它不仅标志着.NET生态系统向更开放、更多样化的方向迈进&#xff0c;也为更广泛的社区参与和贡献提供了可能。然而&#xff0c;与JVM相比&#xff0c;研究和调优GC&#xff08;垃圾回收&#xff09;算法的工作似乎没有那…

gif格式图片是怎么做的?教你一招在线转换

常见的图片有jpg、png以及gif格式&#xff0c;其中&#xff0c;jpg和png格式的图片我们一般手机拍摄或是接受的都是这两种格式&#xff0c;但是gif格式的图片我们却需要从网上下载。那么&#xff0c;当我们想要自己制作gif动画的时候要怎么操作呢&#xff1f;只需要使用在线制作…

NDK Could NOT find OpenGL (missing: OPENGL_glx_LIBRARY) Ubuntu

1. update 该命令将安装一堆新包&#xff0c;包括gcc&#xff0c;g 和make。 sudo apt-get update sudo apt-get install -y build-essential2. install OpenGL依赖库 sudo apt-get install freeglut3-dev sudo apt-get install libgl1-mesa-dev sudo apt-get install libxt-…

MAE实战:使用MAE提高主干网络的精度(一)

摘要 MAE已经出来有几年了&#xff0c;很多人还不知道怎么去使用&#xff0c;本文通过两个例子说明一下。分两部分&#xff0c;一部分介绍一个简单的例子&#xff0c;让大家了解MAE训练的流程。一部分是一个新的模型&#xff0c;让大家了解如何将自己的模型加入MAE。 论文标…

【BBF系列协议】TR156 在TR101环境中使用GPON Access

TR-156 在TR-101环境中使用GPON Access 执行摘要 TR-101提供了一种基于以太网的体系结构,它已成为针对使用DSL作为宽带接入技术的住宅和企业客户的三联游戏部署的全球标准。然而,许多TR-101的体系结构规范都是与访问无关的,而且它们今天也与其他访问技术一起被广泛使用,特…

后端课程接口

路由&#xff1a;course/urls.py router.register(categories, views.CourseCategoryViewSet, categories) # 分类 router.register(free, views.CourseViewSet, free) # 课程视图&#xff1a;course/views.py from rest_framework.viewsets import GenericViewSet from re…

steam搬砖项目赚钱吗?操作流程看这一篇就够了

很多人应该听说过steam&#xff0c;它是国外一款知名的游戏社交平台&#xff0c;也是目前世界上最大的游戏平台之一。而steam搬砖项目&#xff0c;关键就是靠信息差。我们要做的就是在steam以低价买入道具装备&#xff0c;然后上架到网易buff卖出&#xff0c;赚取差价。 什么人…

【Mapstruct】MapStruct实战:简化Java Bean映射

虽然早就在用mapstruct了&#xff0c;但因为要快速原型开发&#xff0c;天天写builder模式&#xff0c;感觉太长了&#xff0c;不好看&#xff0c;&#xff08;然后最近被同事说丑了 &#xff09;&#xff0c;感觉还是做个总结&#xff0c;怒转mapstruct 问题背景或前提知识 …

【PLC一体机】PLC一体机中如何实现触摸屏和PC电脑的通讯

博主今天准备把之前买的PLC一体机拿出来玩一下&#xff0c;翻看以前的博文&#xff0c;发现没有记录分享PLC一体机中如何实现触摸屏程序下载的内容。 如之前博文介绍的那样&#xff0c;PLC一体机由PLC和触摸屏两部分集成的设备&#xff0c;因此设备内部已经做好了PLC和触摸屏之…

C++拷贝构造函数、赋值运算符重载

1.拷贝构造函数 拷贝构造函数的写法如图所示 调用方式如下 接下来我来说说它的特征 1.1特征 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用(一般常用const修饰)&#xff0c;在用已存在的类类型对象创建新对象时由编译器自动调用。 拷贝构造函…

【SpringBoot1】Spring Boot是如何推断你的工程类型的

我们的工程一般都是基于java的Servlet的&#xff0c;但是除此之外还有其他类型的&#xff0c; 在WebApplicationType里定义了常见的类型&#xff1a; public enum WebApplicationType {/*** The application should not run as a web application and should not start an* e…