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
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 就已经被调用了
我们真正的每一个节点上面,如果有绑定这个事件,它就会调用它的一个回调
这就是事件触发的整个流程, 非常的繁琐,有各种各样的方法,嵌套的调用