长春网站建设推荐网诚传媒,临沂酒店建设信息网站,做童装在哪个网站找客户,网络系统安全原则热身准备
明确几个概念
在React17.0.3版本中#xff1a;
所有事件都是委托在id root的DOM元素中#xff08;网上很多说是在document中#xff0c;17版本不是了#xff09;#xff1b;在应用中所有节点的事件监听其实都是在id root的DOM元素中触发#xff1b;React自…热身准备
明确几个概念
在React17.0.3版本中
所有事件都是委托在id root的DOM元素中网上很多说是在document中17版本不是了在应用中所有节点的事件监听其实都是在id root的DOM元素中触发React自身实现了一套事件冒泡捕获机制React实现了合成事件SyntheticEventReact在17版本不再使用事件池了网上很多说使用了对象池来管理合成事件对象的创建销毁那是16版本及之前事件一旦在id root的DOM元素中委托其实是一直在触发的只是没有绑定对应的回调函数
事件系统角色划分
事件注册registerEvents事件监听listenToAllSupportedEvents事件合成SyntheticBaseEvent事件派发dispatchEvent
事件注册
事件注册是自执行的也就是React自身进行调用的
// 注册React事件
registerSimpleEvents();registerEvents$2();
registerEvents$1();
registerEvents$3();
registerEvents(); React事件就是在组件中调用的onClick这种写法的事件。上面分为5个函数写主要是区分不同的事件注册逻辑但是最后都会添加到allNativeEvents的Set数据结构中。
registerSimpleEvents
这里会注册大部分事件它们在React被定义为顶级事件。
它们分为三类
离散事件discreteEvent常见的如click, keyup, change用户阻塞事件userBlocking常见的如dragEnter, mouseMove, scroll连续事件continuous常见的如error, progress, load, 它们的优先级排序
0离散事件 1用户阻塞事件 2连续事件
它们会注册冒泡和捕获阶段两个事件。
registerEvents$2
注册类似onMouseEnteronMouseLeave单阶段事件只注册冒泡阶段事件。
registerEvents$1
注册onChange相关事件注册冒泡和捕获阶段两个事件。
registerEvents$3
注册onSelect相关事件注册冒泡和捕获阶段两个事件。
registerEvents
注册onBeforeInputonCompositionUpdate等相关事件注册冒泡和捕获阶段两个事件。
事件监听
在React源码系列之二React的渲染机制曾提到过React在开始渲染前会为应用创建一个fiberRoot作为应用的根节点。在创建fiberRoot还会做一件事就是
listenToAllSupportedEvents(rootContainerElement); 从字面就能理解这个函数是做事件监听的其中rootContainerElement参数就是应用中的id root的DOM元素。
该函数主要遍历上面事件注册添加到allNativeEvents的事件按照一定规则区分冒泡阶段捕获阶段区分有无副作用进行监听监听的api还是addEventListener:
// 监听冒泡阶段事件
function addEventBubbleListener(target, eventType, listener) {target.addEventListener(eventType, listener, false);return listener;
}
// 监听捕获阶段事件
function addEventCaptureListener(target, eventType, listener) {target.addEventListener(eventType, listener, true);return listener;
} 代码中的target就是id root的DOM元素。
注意上面监听的listener是一个事件派发器并不是真实的浏览器事件或你写的事件回调函数。 不要搞混淆了。
事件派发
上面提到事件一旦在id root的DOM元素中委托其实是一直在触发的只是没有绑定对应的回调函数。
意思是当我们把鼠标移入我们的应用页面中时这时就在派发事件了因为页面的DOM元素是有监听mousemove之类的事件的。
那问题来了React是如何得知我们给事件绑定了回调函数并触发对应的回调函数的
带着这个问题我们来研究下事件派发。
要讲事件派发还得提下事件监听阶段监听的listener它实际是下面这玩意
function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) {var eventPriority getEventPriorityForPluginSystem(domEventName);var listenerWrapper;switch (eventPriority) {case DiscreteEvent:listenerWrapper dispatchDiscreteEvent;break;case UserBlockingEvent:listenerWrapper dispatchUserBlockingUpdate;break;case ContinuousEvent:default:listenerWrapper dispatchEvent;break;}return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
} 和事件注册一样listener也分为dispatchDiscreteEvent, dispatchUserBlockingUpdate, dispatchEvent三种。它们之间的主要区别是执行优先级还有discreteEvent涉及到要清除之前的discreteEvent问题所以做了区分。但是它们最后都会调用dispatchEvent。相关参考视频讲解进入学习
所以事件派发的角色应该是dispatchEvent
function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {var allowReplay true;allowReplay (eventSystemFlags IS_CAPTURE_PHASE) 0;// 如果有离散事件正在执行会排队顺序执行if (allowReplay hasQueuedDiscreteEvents() isReplayableDiscreteEvent(domEventName)) {domEventName, eventSystemFlags, targetContainer, nativeEvent);return;}// 尝试事件派发如果成功就不用执行下面的代码了var blockedOn attemptToDispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent);// 尝试事件派发成功if (blockedOn null) {if (allowReplay) {// 清除连续事件队列clearIfContinuousEvent(domEventName, nativeEvent);}return;}if (allowReplay) {if (isReplayableDiscreteEvent(domEventName)) {queueDiscreteEvent(blockedOn, domEventName, eventSystemFlags, targetContainer, nativeEvent);return;}if (queueIfContinuousEvent(blockedOn, domEventName, eventSystemFlags, targetContainer, nativeEvent)) {return;} clearIfContinuousEvent(domEventName, nativeEvent);} dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, null, targetContainer);
} 介绍下dispatchEvent的几个参数
domEventName DOM事件名称如click不是onClickeventSystemFlags事件系统标记targetContaineridroot的DOM元素nativeEvent原生事件来自addEventListener)
在attemptToDispatchEvent中 根据nativeEvent.target找到真正触发事件的DOM元素并根据DOM元素找到对应的fiber节点判断fiber节点的类型以及是否已渲染来决定是否要派发事件。
在一系列判断通过后就开始真正的事件处理了
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {// 获取触发事件的DOM元素var nativeEventTarget getEventTarget(nativeEvent);// 初始化事件派发队列var dispatchQueue [];// 合成事件extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);// 按事件派发队列执行事件派发processDispatchQueue(dispatchQueue, eventSystemFlags);
} 在extractEvents$5中会进行事件合成放在下面单独讲。
在processDispatchQueue会根据事件阶段冒泡或捕获来决定是正序还是倒序遍历合成事件中的listeners。
接下来就比较简单了。 遍历listeners执行上面的listener。
合成事件
在合成事件中会根据domEventName来决定使用哪种类型的合成事件。
以click为例当我们点击页面的某个元素时React会根据原生事件nativeEvent找到触发事件的DOM元素和对应的fiber节点。并以该节点为孩子节点往上查找找到包括该节点及以上所有的click回调函数创建dispatchListener并添加到listeners数组中。
// dispatchListener
{instance: instance,// 事件所在的fiber节点listener: listener,// 事件回调函数currentTarget: currentTarget// 事件对应的DOM元素} 当向上查找完成后会基于click类型的合成事件类创建事件
// 创建合成事件实例
var _event new SyntheticEventCtor(reactName, reactEventType, null, nativeEvent, nativeEventTarget);
// 事件派发队列添加事件
dispatchQueue.push({event: _event, // 合成事件实例listeners: _listeners// 同类型事件的集合数组
}); 看下SyntheticEventCtor
// Interface根据事件类型有所不同
function createSyntheticEvent(Interface) {// 合成事件构造函数function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget) {this._reactName reactName;this._targetInst targetInst;this.type reactEventType;this.nativeEvent nativeEvent;this.target nativeEventTarget;this.currentTarget null;// React根据不同事件类型写了对应的属性接口这里基于接口将原生事件上的属性clone到构造函数中for (var _propName in Interface) {... }var defaultPrevented nativeEvent.defaultPrevented ! null ? nativeEvent.defaultPrevented : nativeEvent.returnValue false;if (defaultPrevented) {this.isDefaultPrevented functionThatReturnsTrue;} else {this.isDefaultPrevented functionThatReturnsFalse;}this.isPropagationStopped functionThatReturnsFalse;return this;}_assign(SyntheticBaseEvent.prototype, {// 阻止默认事件preventDefault: function () {...},// 阻止捕获和冒泡阶段中当前事件的进一步传播stopPropagation: function () {...},// 合成事件不使用对象池了这个事件是空的没有意义保存是为了向下兼容不报错。persist: function () {},isPersistent: functionThatReturnsTrue});return SyntheticBaseEvent;
} 看到这里我们基本能弄明白合成事件是个什么东西了。
React合成事件是将同类型的事件找出来基于这个类型的事件React通过代码定义好的类型事件的接口和原生事件创建相应的合成事件实例并重写了preventDefault和stopPropagation方法。
这样同类型的事件会复用同一个合成事件实例对象节省了单独为每一个事件创建事件实例对象的开销这就是事件的合成。
捕获和冒泡
事件派发分为两个阶段执行 捕获阶段和冒泡阶段。
在上面事件合成中讲过React会根据事件触发的fiber节点向上查找将上面的同类型事件添加到队列中这样天然就有了一个冒泡的顺序从最底层向上冒泡。如果倒序过来遍历就是捕获的顺序。
所以React实现冒泡和捕获就很简单了只需要根据事件派发的阶段判断是冒泡阶段还是捕获阶段来决定是正序遍历listeners还是倒序遍历就行了。
总结
说是讲React的合成事件实际上讲了React的事件系统。做下总结
React的事件系统分为几个部分
事件注册* 事件监听* 事件合成* 事件派发 事件系统流程1.在React代码执行时内部会自动执行事件的注册2.第一次渲染创建fiberRoot时会进行事件的监听所有的事件通过addEventListener委托在idroot的DOM元素上进行监听3.在我们触发事件时会进行事件合成同类型事件复用一个合成事件类实例对象4.最后进行事件的派发执行我们代码中的事件回调函数当然由于篇幅问题这里也是对React事件系统的一个精简剖析可能忽略了一些地方欢迎指正。
看完这篇文章 我们可以弄明白下面这几个问题
1.React事件委托在哪 2.React合成事件是什么 3.React合成事件是怎么实现的 4.React是怎么实现冒泡和捕获的 5.React合成事件是使用的原生事件吗 6.React事件系统分为哪几个部分
最后
整理了一套《前端大厂面试宝典》包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法一共201道面试题并对每个问题作出了回答和解析。 有需要的小伙伴可以点击文末卡片领取这份文档无偿分享
部分文档展示
文章篇幅有限后面的内容就不一一展示了
有需要的小伙伴可以点下方卡片免费领取