event 事件触发过程
1 )概述
- 在之前事件绑定时,绑定的是两个方法
- 一个是
dispatchInteractiveEvent
- 另外一个
dispatchEvent
- 一个是
- 其实它们调用的方法都是差不多的,一开始会有一点小的区别
2 )源码
定位到 packages/react-dom/src/events/ReactDOMEventListener.js#L165
进入 trapCapturedEvent
js
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
js
// 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
js
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
方法jsimport {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; }
- 这个方法主要是为了兼容浏览器API获取 target 对象, 最终根据是否是
TEXT_NODE
返回 target.parentNode 或 target
- 这个方法主要是为了兼容浏览器API获取 target 对象, 最终根据是否是
-
进入
getClosestInstanceFromNode
jsexport 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
jsconst 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
js// 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: [], }; }
- 这个函数就是一缓存机制
- 最终我们在执行完整个react事件之后,会把这个对象再归还到这个pool里面来进行一个存储
- 让浏览器的js运行环境不去删除这个对象,也让我们在后期不需要去重新创建一个对象,
- 以减少这个对象声明和对象垃圾回收的一个性能开销
-
进入
batchedUpdates
js// 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
jsfunction 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
jsfunction 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
jsexport 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
js// 把两个值(数组)合并,形成一个数组 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
js// 对于传进来的一个数组,判断它是否是一个数组 // 如果是数组就会去对每一项调用这个 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
jsconst 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
jsfunction 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
js// 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 就已经被调用了
-
我们真正的每一个节点上面,如果有绑定这个事件,它就会调用它的一个回调
-
这就是事件触发的整个流程, 非常的繁琐,有各种各样的方法,嵌套的调用