赞
踩
1 )概述
dispatchInteractiveEvent
dispatchEvent
2 )源码
定位到 packages/react-dom/src/events/ReactDOMEventListener.js#L165
进入 trapCapturedEvent
export function trapCapturedEvent( topLevelType: DOMTopLevelEventType, element: Document | Element, ) { if (!element) { return null; } // 注意这里,根据是否是 Interactive 类型的事件,调用的不同的回调,最终赋值给 dispatch const dispatch = isInteractiveTopLevelEventType(topLevelType) ? dispatchInteractiveEvent : dispatchEvent; addEventCaptureListener( element, getRawEventName(topLevelType), // Check if interactive and wrap in interactiveUpdates // 这边的topleveltype呢,是我们在进行 dom 的事件绑定的时候已经通过 bind 给它绑定好了 // 在绑定事件的时候,就已经确定了一个值, 比如说是onChange这类的toplevel的事件名称 dispatch.bind(null, topLevelType), ); }
2.1 先看 dispatchInteractiveEvent
定位到 packages/react-dom/src/events/ReactDOMEventListener.js#L184
// packages/react-dom/src/events/ReactDOMEventListener.js#L184 // nativeEvent,就是我们事件触发的时候,我们的domm的事件体系会给我们一个event对象 // 可以通过它来进入默认行为之类的这么一个事件对象 function dispatchInteractiveEvent(topLevelType, nativeEvent) { interactiveUpdates(dispatchEvent, topLevelType, nativeEvent); } // packages/events/ReactGenericBatching.js#L55 export function interactiveUpdates(fn, a, b) { return _interactiveUpdatesImpl(fn, a, b); } // packages/events/ReactGenericBatching.js#L23 let _interactiveUpdatesImpl = function(fn, a, b) { return fn(a, b); };
dispatchInteractiveEvent
最终还是要调用 dispatchEvent
, 只是多包了一层2.2 进入 dispatchEvent
定位到 packages/react-dom/src/events/ReactDOMEventListener.js#L188
export function dispatchEvent( topLevelType: DOMTopLevelEventType, nativeEvent: AnyNativeEvent, ) { if (!_enabled) { return; } // 注意这里 const nativeEventTarget = getEventTarget(nativeEvent); let targetInst = getClosestInstanceFromNode(nativeEventTarget); // 存在 符合条件的,并且没有被挂载 if ( targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst) ) { // If we get an event (ex: img onload) before committing that // component's mount, ignore it for now (that is, treat it as if it was an // event on a non-React tree). We might also consider queueing events and // dispatching them after the mount. targetInst = null; // 这个 target 置空 } // 这里只是一个对象,用于携带信息 const bookKeeping = getTopLevelCallbackBookKeeping( topLevelType, nativeEvent, targetInst, ); try { // Event queue being processed in the same cycle allows // `preventDefault`. batchedUpdates(handleTopLevel, bookKeeping); } finally { releaseTopLevelCallbackBookKeeping(bookKeeping); } }
首先它要获取 nativeEventTarget
,这个 target 就是我们 event 对象上面的 target
它是出于对各种系统的一个兼容调用的一个方法来进行一个 Polyfill
进入这个 getEventTarget
方法
import {TEXT_NODE} from '../shared/HTMLNodeType'; /** * Gets the target node from a native browser event by accounting for * inconsistencies in browser DOM APIs. * * @param {object} nativeEvent Native browser event. * @return {DOMEventTarget} Target node. */ function getEventTarget(nativeEvent) { // 这个是对 IE9 的兼容 // Fallback to nativeEvent.srcElement for IE9 // https://github.com/facebook/react/issues/12506 let target = nativeEvent.target || nativeEvent.srcElement || window; // Normalize SVG <use> element events #4963 if (target.correspondingUseElement) { target = target.correspondingUseElement; } // Safari may fire events on text nodes (Node.TEXT_NODE is 3). // @see http://www.quirksmode.org/js/events_properties.html return target.nodeType === TEXT_NODE ? target.parentNode : target; }
TEXT_NODE
返回 target.parentNode 或 target进入 getClosestInstanceFromNode
export function getClosestInstanceFromNode(node) { // 存在,直接 return if (node[internalInstanceKey]) { return node[internalInstanceKey]; // 这个就是 初始化dom节点的时候,在node上插入这么一个key, 来指定对应的 fiber 对象 } // 不存在,一直找 parentNode while (!node[internalInstanceKey]) { if (node.parentNode) { node = node.parentNode; } else { // Top of the tree. This node must not be part of a React tree (or is // unmounted, potentially). return null; } } // 这个 inst 就是一个 fiber 对象, 找到是 HostComponent 或 HostText 类型的 let inst = node[internalInstanceKey]; if (inst.tag === HostComponent || inst.tag === HostText) { // In Fiber, this will always be the deepest root. return inst; } return null; }
进入 isFiberMounted
const MOUNTING = 1; const MOUNTED = 2; const UNMOUNTED = 3; function isFiberMountedImpl(fiber: Fiber): number { let node = fiber; // 不存在,说明是即将插入或没有插入的节点 if (!fiber.alternate) { // If there is no alternate, this might be a new tree that isn't inserted // yet. If it is, then it will have a pending insertion effect on it. // 这种是即将要插入的 if ((node.effectTag & Placement) !== NoEffect) { return MOUNTING; } // 如果当前不是,向上找父节点,也是即将插入的 while (node.return) { node = node.return; if ((node.effectTag & Placement) !== NoEffect) { return MOUNTING; } } } else { // 存在 alternate, 继续向上找 while (node.return) { node = node.return; } } // 如果上级存在,并且是 HostRoot 说明已经被挂载了 if (node.tag === HostRoot) { // TODO: Check if this was a nested HostRoot when used with // renderContainerIntoSubtree. return MOUNTED; } // If we didn't hit the root, that means that we're in an disconnected tree // that has been unmounted. // 其他情况都是未挂载的 return UNMOUNTED; } export function isFiberMounted(fiber: Fiber): boolean { return isFiberMountedImpl(fiber) === MOUNTED; }
进入 getTopLevelCallbackBookKeeping
// packages/react-dom/src/events/ReactDOMEventListener.js#L50 const CALLBACK_BOOKKEEPING_POOL_SIZE = 10; const callbackBookkeepingPool = []; // Used to store ancestor hierarchy in top level callback function getTopLevelCallbackBookKeeping( topLevelType, nativeEvent, targetInst, ): { topLevelType: ?DOMTopLevelEventType, nativeEvent: ?AnyNativeEvent, targetInst: Fiber | null, ancestors: Array<Fiber>, } { if (callbackBookkeepingPool.length) { const instance = callbackBookkeepingPool.pop(); instance.topLevelType = topLevelType; instance.nativeEvent = nativeEvent; instance.targetInst = targetInst; return instance; } return { topLevelType, nativeEvent, targetInst, ancestors: [], }; }
进入 batchedUpdates
// packages/events/ReactGenericBatching.js#L29 let _batchedUpdatesImpl = function(fn, bookkeeping) { return fn(bookkeeping); }; let isBatching = false; export function batchedUpdates(fn, bookkeeping) { if (isBatching) { // If we are currently inside another batch, we need to wait until it // fully completes before restoring state. return fn(bookkeeping); } isBatching = true; try { return _batchedUpdatesImpl(fn, bookkeeping); } finally { // Here we wait until all updates have propagated, which is important // when using controlled components within layers: // https://github.com/facebook/react/issues/1698 // Then we restore state of any controlled component. isBatching = false; // 下面的代码其实就跟 input 控制输入 有关的 // 我们知道在 react 当中我们通过 value 给 input 标签上面去绑定了值,直接在外部输入的值 // 如果没有 onChange 事件来处理这个 state,它这个值输不进去的,这就是 input的控制输入 的一个概念 // 其控制就是在这里实现的 const controlledComponentsHavePendingUpdates = needsStateRestore(); if (controlledComponentsHavePendingUpdates) { // If a controlled event was fired, we may need to restore the state of // the DOM node back to the controlled value. This is necessary when React // bails out of the update without touching the DOM. _flushInteractiveUpdatesImpl(); restoreStateIfNeeded(); } } } // packages/events/ReactControlledComponent.js#L58 export function needsStateRestore(): boolean { return restoreTarget !== null || restoreQueue !== null; } export function restoreStateIfNeeded() { if (!restoreTarget) { return; } const target = restoreTarget; const queuedTargets = restoreQueue; restoreTarget = null; restoreQueue = null; restoreStateOfTarget(target); if (queuedTargets) { for (let i = 0; i < queuedTargets.length; i++) { restoreStateOfTarget(queuedTargets[i]); } } }
batchedUpdates 里面调用的方法是 handleTopLevel
function handleTopLevel(bookKeeping) { let 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. let ancestor = targetInst; do { // 如果不存在,就push,并且跳出循环 if (!ancestor) { bookKeeping.ancestors.push(ancestor); break; } // 如果存在了,找到 Root 挂载节点 const root = findRootContainerNode(ancestor); // 不存在挂载节点,则跳出 if (!root) { break; } // 存在挂载节点 bookKeeping.ancestors.push(ancestor); // 这个方法之前已经分析了,找到存储的 fiber 对象 // 在这里, 这个时候传入了是这个root,对于大部分情况来讲, HostRoot 已经没有上级的节点,会是处于react的一个应用当中 // 也可能是会有这种情况的, 比如通过一些比较 hack 的一些方式, 我们在react应用里面再去渲染了一个新的react应用 // 这种方法也可能是存在的,所以在这边就尝试了这么去做 // 因为它们两个如果确实出现这种情况,那么它们的root节点是不一样的 // 事件正常来讲是要冒泡到最外层的那个root树的最顶上的 // 所以这种情况需要去调用这个方法,去把所有的 ancestor 给找到,并且推到这个 bookKeeping.ancestors 里面 ancestor = getClosestInstanceFromNode(root); } while (ancestor); // 对于每一个 concest,去获取它的 targetInst 对大部分情况, 其实就是我们这个 targetInst // 就是我们这个事件触发的那一个节点 event.target 对应的那个fiber对象 for (let i = 0; i < bookKeeping.ancestors.length; i++) { targetInst = bookKeeping.ancestors[i]; runExtractedEventsInBatch( bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent), ); } }
findRootContainerNode
function findRootContainerNode(inst) { // TODO: It may be a good idea to cache this to prevent unnecessary DOM // traversal, but caching is difficult to do correctly without using a // mutation observer to listen for all DOM changes. // 向上找 while (inst.return) { inst = inst.return; } // 如果不是 HostRoot 则失败 if (inst.tag !== HostRoot) { // This can happen if we're in a detached tree. return null; } // 找到 react 应用挂载的 dom节点 return inst.stateNode.containerInfo; }
runExtractedEventsInBatch
export function runExtractedEventsInBatch( topLevelType: TopLevelType, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: EventTarget, ) { // 获取所有事件 const events = extractEvents( topLevelType, targetInst, nativeEvent, nativeEventTarget, ); runEventsInBatch(events); } // 生成事件对象 function extractEvents( topLevelType: TopLevelType, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: EventTarget, ): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null { let events = null; // 调用每一个plugin, 在内部,调用 possiblePlugin.extractEvents 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) { const extractedEvents = possiblePlugin.extractEvents( topLevelType, targetInst, nativeEvent, nativeEventTarget, ); // 如果存在,则插入到对象中 if (extractedEvents) { events = accumulateInto(events, extractedEvents); } } } return events; } let eventQueue: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent) = null; export function runEventsInBatch( events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null, ) { 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. const processingEventQueue = eventQueue; eventQueue = null; if (!processingEventQueue) { return; } // 对这个 processingEventQueue, 可能是数组,也可能只有一个event的这个内容 // 对它调用了这个 executeDispatchesAndReleaseTopLevel 方法 forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel); invariant( !eventQueue, 'processEventQueue(): Additional events were enqueued while processing ' + 'an event queue. Support for this has not yet been implemented.', ); // This would be a good time to rethrow if any of the event handlers threw. rethrowCaughtError(); }
accumulateInto
// 把两个值(数组)合并,形成一个数组 function accumulateInto<T>( current: ?(Array<T> | T), next: T | Array<T>, ): T | Array<T> { invariant( next != null, 'accumulateInto(...): Accumulated items must not be null or undefined.', ); if (current == null) { return next; } // Both are not empty. Warning: Never call x.concat(y) when you are not // certain that x is an Array (x could be a string with concat method). // 合并两个数组 if (Array.isArray(current)) { if (Array.isArray(next)) { current.push.apply(current, next); return current; } current.push(next); return current; } if (Array.isArray(next)) { // A bit too dangerous to mutate `next`. return [current].concat(next); } return [current, next]; }
forEachAccumulated
// 对于传进来的一个数组,判断它是否是一个数组
// 如果是数组就会去对每一项调用这个 callback
// 如果它不是一个数组,就直接调用这个callback传入这个值就可以了
function forEachAccumulated<T>(
arr: ?(Array<T> | T),
cb: (elem: T) => void,
scope: ?any,
) {
if (Array.isArray(arr)) {
arr.forEach(cb, scope);
} else if (arr) {
cb.call(scope, arr);
}
}
executeDispatchesAndReleaseTopLevel
const executeDispatchesAndReleaseTopLevel = function(e) { return executeDispatchesAndRelease(e); }; const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) { if (event) { executeDispatchesInOrder(event); if (!event.isPersistent()) { event.constructor.release(event); } } }; // packages/events/EventPluginUtils.js#L76 // 最后真正调用这个事件的地方,其实它里面整个过程会非常的复杂,有各种各样的函数的嵌套调用 // 其实里面可能有将近一半的函数都是工具类型的函数,注意阅读代码时,别被绕进去 export function executeDispatchesInOrder(event) { // 这边获取了 dispatchListeners 以及 dispatchInstances 这两个数据 // 这两个数据都来自 event 对象,上面会挂载了一个叫 _dispatchListeners 和 _dispatchInstances // 这两个东西都是数组,并且是一一对应的关系 const dispatchListeners = event._dispatchListeners; const dispatchInstances = event._dispatchInstances; // 忽略 if (__DEV__) { validateEventDispatches(event); } // 然后,它去判断一下是否是一个数组, 如果是一个数组对它进行一个遍历 if (Array.isArray(dispatchListeners)) { for (let i = 0; i < dispatchListeners.length; i++) { // 并且判断一下 event 是否已经 isPropagationStopped,就是我们已经停止冒泡了 // 如果是的话,我们就直接 break if (event.isPropagationStopped()) { break; } // Listeners and Instances are two parallel arrays that are always in sync. executeDispatch(event, dispatchListeners[i], dispatchInstances[i]); } // 不是数组,但是存在 } else if (dispatchListeners) { // 调用这个方法 executeDispatch(event, dispatchListeners, dispatchInstances); } // 重置 event._dispatchListeners = null; event._dispatchInstances = null; }
executeDispatch
function executeDispatch(event, listener, inst) {
const type = event.type || 'unknown-event';
event.currentTarget = getNodeFromInstance(inst); // 从 inst 上获取 node 节点
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event); // 这里回调最终被触发调用
event.currentTarget = null;
}
invokeGuardedCallbackAndCatchFirstError
// packages/shared/ReactErrorUtils.js#L67 export function invokeGuardedCallbackAndCatchFirstError< A, B, C, D, E, F, Context, >( name: string | null, func: (a: A, b: B, c: C, d: D, e: E, f: F) => void, context: Context, a: A, b: B, c: C, d: D, e: E, f: F, ): void { // 这个函数之前遇到过,目前不再展开 invokeGuardedCallback.apply(this, arguments); if (hasError) { const error = clearCaughtError(); if (!hasRethrowError) { hasRethrowError = true; rethrowError = error; } } }
到这里为止,这边的 listener 就已经被调用了
我们真正的每一个节点上面,如果有绑定这个事件,它就会调用它的一个回调
这就是事件触发的整个流程, 非常的繁琐,有各种各样的方法,嵌套的调用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。