You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
var dependencies = registrationNameDependencies[registrationName];
for (var i = 0; i < dependencies.length; i++) {
var dependency = dependencies[i];
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.
var isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
if (!isMediaEvent) {
trapBubbledEvent(dependency, mountAt);
}
break;
}
isListening[dependency] = true;
}
}
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
batchedUpdates(handleTopLevel, bookKeeping);
} finally {
//释放bookKeeping
releaseTopLevelCallbackBookKeeping(bookKeeping);
}
function handleTopLevel(bookKeeping) {
var targetInst = bookKeeping.targetInst;
// Loop through the hierarchy, in case there's any nested components.
// It's important that we build the array of ancestors before calling any
// event handlers, because event handlers can modify the DOM, leading to
// inconsistencies with ReactMount's node cache. See #1105.
var ancestor = targetInst;
do {
if (!ancestor) {
bookKeeping.ancestors.push(ancestor);
break;
}
var root = findRootContainerNode(ancestor);
if (!root) {
break;
}
bookKeeping.ancestors.push(ancestor);
ancestor = getClosestInstanceFromNode(root);
} while (ancestor);
for (var i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i];
runExtractedEventsInBatch(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));
}
}
function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
var events = null;
for (var i = 0; i < plugins.length; i++) {
// Not every plugin in the ordering may be loaded at runtime.
var possiblePlugin = plugins[i];
if (possiblePlugin) {
var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
return events;
}
function traverseTwoPhase(inst, fn, arg) {
var path = [];
while (inst) {
path.push(inst);
inst = getParent(inst);
}
var i = void 0;
for (i = path.length; i-- > 0;) {
fn(path[i], 'captured', arg);
}
for (i = 0; i < path.length; i++) {
fn(path[i], 'bubbled', arg);
}
}
fn就是下面的accumulateDirectionalDispatches
function accumulateDirectionalDispatches(inst, phase, event) {
{
!inst ? warningWithoutStack$1(false, 'Dispatching inst must not be null') : void 0;
}
var listener = listenerAtPhase(inst, event, phase);
if (listener) {
event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
function runEventsInBatch(events, simulated) {
if (events !== null) {
eventQueue = accumulateInto(eventQueue, events);
}
// Set `eventQueue` to null before processing it so that we can tell if more
// events get enqueued while processing.
var processingEventQueue = eventQueue;
eventQueue = null;
if (!processingEventQueue) {
return;
}
if (simulated) {
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseSimulated);
} else {
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
}
!!eventQueue ? invariant(false, 'processEventQueue(): Additional events were enqueued while processing an event queue. Support for this has not yet been implemented.') : void 0;
// This would be a good time to rethrow if any of the event handlers threw.
rethrowCaughtError();
}
function forEachAccumulated(arr, cb, scope) {
if (Array.isArray(arr)) {
arr.forEach(cb, scope);
} else if (arr) {
cb.call(scope, arr);
}
}
var executeDispatchesAndReleaseTopLevel = function (e) {
return executeDispatchesAndRelease(e, false);
};
var executeDispatchesAndRelease = function (event, simulated) {
if (event) {
executeDispatchesInOrder(event, simulated);
if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
function executeDispatchesInOrder(event, simulated) {
var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;
{
validateEventDispatches(event);
}
if (Array.isArray(dispatchListeners)) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
// Listeners and Instances are two parallel arrays that are always in sync.
executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);
}
} else if (dispatchListeners) {
executeDispatch(event, simulated, dispatchListeners, dispatchInstances);
}
event._dispatchListeners = null;
event._dispatchInstances = null;
}
if (!event.isPersistent()) {
event.constructor.release(event);
}
如果不持有事件对象的话就释放事件,调用
function releasePooledEvent(event) {
var EventConstructor = this;
!(event instanceof EventConstructor) ? invariant(false, 'Trying to release an event instance into a pool of a different type.') : void 0;
event.destructor();
if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
EventConstructor.eventPool.push(event);
}
}
事件注册
直接从finalizeInitialChildren方法看起
改方法转而调用setInitialProperties ,从其名字可以看出是设置初始属性的。遍历props对象,给DOM设置一系列的属性,如style、class、autoFocus,innerHTML等,其中有一个case是处理事件的
其中的 registrationNameModules这个变量 在React事件(一)中已经介绍过了,是注册的事件名对应的事件插件,注册的事件名是React事件名,以on开头,如 ‘onClick’。
加入添加了onClick事件,那么propKey ===onClick
就会执行ensureListeningTo方法
这个方法首先判断rootContainerElement是不是一个 document或者 Fragment,二者都不是的话,就设置doc为rootContainerElement.ownerDocument, 也就是当前节点的顶层的 document 对象。
可以看到事件委托就是在这里实现的,所有的事件最终都会被委托到 document 或者 fragment上去,大部分情况下都是 document。
接着看listenTo方法
dependencies 存储的是原生事件名称,可以看到,有两个方法,
像scroll,focus, blur,cancel,close,会调用trapCapturedEvent
一般事件默认走default分支,调用trapBubbledEvent方法
二者唯一的不同之处就在于,对于最终的合成事件,前者注册捕获阶段的事件监听器,而后者则注册冒泡阶段的事件监听器
trapBubbledEvent
dispatch就是dispatchEvent或者dispatchInteractiveEvent,dispatchInteractiveEvent最终也会执行dispatchEvent。
参数
该方法用 addEventListener给 document注册了一个冒泡事件
事件分发
当事件触发的时候会调用刚刚注册的listener
接着调用dispatchEvent
通过nativeEvent找到对应的DOM实例和fiber node ;
其中在创建dom 的时候,会将对应的fiber node 设置到dom的internalInstanceKey属性上,即targetInst
接着生成bookKeeping对象,
接下来进入批量更新阶段,其核心方法就是handleTopLevel
在处理事件回调之前,根据当前的targetInst向上遍历,得到其所有的父级组件,存在ancestors中,我们知道事件委托到了document上,无论是冒泡还是捕获事件都有触发次序的问题,所以ancestors中,子元素排在了父元素前面。接下来就遍历ancestors,执行runExtractedEventsInBatch方法
runExtractedEventsInBatch
runExtractedEventsInBatch这个方法中调用了两个方法
构造合成事件
这个plugins是个全局变量,在第一篇文章中有介绍,按插件注入顺序存储了各个事件插件。
遍历plugins,调用每个插件的extractEvents方法,找到对应的事件构造器,
getPooled是 EventConstructor上的一个方法,这个方法是在 EventConstructor初始化的时候挂上去的,其实就是 getPooledEvent。
getPooled就是从 event对象池中取出合成事件,将所有的事件缓存在对象池中,可以大大降低对象创建和销毁的时间,提升性能。
构造的react event 既有原生事件的接口属性,也有react自己的属性。
这个方法及其相关的方法在第二篇有介绍。请看React 事件(二)
构造完事件对象之后,调用 accumulateTwoPhaseDispatches方法,其中会调用traverseTwoPhase
traverseTwoPhase
向上遍历当前fiber node ,存储在path中。然后分别对捕获和冒泡阶段进行处理。
fn就是下面的accumulateDirectionalDispatches
listenerAtPhase 就是专门从当前的fibernode 上获取事件回调的。
取到回调之后,保存到事件event的 _dispatchListeners属性上,并且将当前元素及其父元素的FiberNode 保存到event的 _dispatchInstances属性上.
拿到所有与事件相关的元素实例以及事件的回调函数之后,就可以对合成事件进行批量处理了。
事件处理
runEventsInBatch 事件批处理的入口
首先会将当前需要处理的 events事件,与之前没有处理完毕的队列调用 accumulateInto方法按照顺序进行合并,组合成一个新的队列,因为之前可能就存在还没处理完的合成事件,这里就可以执行了。
遍历 dispatchListeners ,如果event.isPropagationStopped()为 true,则遍历的循环直接 break掉,中断合成事件的向上遍历执行,也就起到了和原生事件调用 stopPropagation相同的效果。如果循环没有被中断,则继续执行 executeDispatch方法。最终会调用
func 就是事件回调 listener;
funcArgs 就是合成的事件对象event;
事件回收
事件执行完毕之后就会做一些清理的工作了。回到executeDispatchesAndRelease方法中,
如果不持有事件对象的话就释放事件,调用
首先释放掉 event上属性占用的内存,然后把清理后的 event对象再放入对象池中,可以被后续事件对象二次利用。
至此 React 事件的一些基本概念和事件机制介绍完了。
The text was updated successfully, but these errors were encountered: