1 ) 概述
possiblePlugin.extractEvents
方法2 )源码
定位到 packages/events/EventPluginHub.js#L172
function extractEvents(
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
let events = null;
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) {
// 这里要去调用 每个 plugin 的 extractEvents 方法
const extractedEvents = possiblePlugin.extractEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
return events;
}
注意这里的 possiblePlugin.extractEvents
我们专门专注下 changeEvent 的这个方法
定位到 packages/react-dom/src/events/ChangeEventPlugin.js#L263
const ChangeEventPlugin = {
eventTypes: eventTypes,
_isInputEventSupported: isInputEventSupported,
// 注意这里
extractEvents: function(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
) {
// 在这个方法里面,拿到了 targetNode
// 因为这边传进来的呢是一个发布对象, 所以要通过这种方法拿到它的 node
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
// 然后,要经过一系列的判断,主要去赋值了不同的 getTargetInstFunc
let getTargetInstFunc, handleEventFunc;
// 如果有返回这个instance,那么我们就可以去创建这个event了,这是什么意思呢?
// 就是说我们这个 event plugin, 在所有的事件触发的过程当中,这个plugin都会被循环调用的
// 它是没有通过事件名称来调用不同的plugin这么一个设置的
// 所以这个判断是要放在每个pluggin里面自己去做。就是说根据这次触发的具体事件是什么?
// 来判断我们要不要为它创建一个event,因为每个plugin在每次事件触发都会被调用
// 如果我们都生成事件,那么明显是不对的,肯定要对自己这个 plugin 关心的事件来去为它生成这个事件
if (shouldUseChangeEvent(targetNode)) {
getTargetInstFunc = getTargetInstForChangeEvent;
} else if (isTextInputElement(targetNode)) {
if (isInputEventSupported) {
getTargetInstFunc = getTargetInstForInputOrChangeEvent;
} else {
// polyfill 的这些先忽略
getTargetInstFunc = getTargetInstForInputEventPolyfill;
handleEventFunc = handleEventsForInputEventPolyfill;
}
} else if (shouldUseClickEvent(targetNode)) {
getTargetInstFunc = getTargetInstForClickEvent;
}
// 基于类型,得到了最终的处理函数
if (getTargetInstFunc) {
const inst = getTargetInstFunc(topLevelType, targetInst);
if (inst) {
// 创建 event
const event = createAndAccumulateChangeEvent(
inst,
nativeEvent,
nativeEventTarget,
);
return event;
}
}
if (handleEventFunc) {
handleEventFunc(topLevelType, targetNode, targetInst);
}
// When blurring, set the value attribute for number inputs
if (topLevelType === TOP_BLUR) {
handleControlledInputBlur(targetNode);
}
},
};
进入 shouldUseChangeEvent
function shouldUseChangeEvent(elem) {
const nodeName = elem.nodeName && elem.nodeName.toLowerCase();
return (
nodeName === 'select' || (nodeName === 'input' && elem.type === 'file')
);
}
getTargetInstFunc = getTargetInstForChangeEvent;
进入 getTargetInstForChangeEvent
function getTargetInstForChangeEvent(topLevelType, targetInst) {
// TOP_CHANGE 就是 change
if (topLevelType === TOP_CHANGE) {
return targetInst;
}
}
// 注意 另外的文件中
// packages/react-dom/src/events/DOMTopLevelEventTypes.js#L41
export const TOP_CHANGE = unsafeCastStringToDOMTopLevelType('change');
// packages/events/TopLevelEventTypes.js#L27
export function unsafeCastStringToDOMTopLevelType(
topLevelType: string,
): DOMTopLevelEventType {
return topLevelType;
}
进入 isTextInputElement
const supportedInputTypes: {[key: string]: true | void} = {
color: true,
date: true,
datetime: true,
'datetime-local': true,
email: true,
month: true,
number: true,
password: true,
range: true,
search: true,
tel: true,
text: true,
time: true,
url: true,
week: true,
};
function isTextInputElement(elem: ?HTMLElement): boolean {
// 获取 nodeName
const nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
// 判断在 input 的时候,是否符合支持的type类型
if (nodeName === 'input') {
return !!supportedInputTypes[((elem: any): HTMLInputElement).type];
}
if (nodeName === 'textarea') {
return true;
}
return false;
}
进入 getTargetInstForInputOrChangeEvent
// packages/react-dom/src/events/ChangeEventPlugin.js#L229
function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
if (topLevelType === TOP_INPUT || topLevelType === TOP_CHANGE) {
return getInstIfValueChanged(targetInst);
}
}
// packages/react-dom/src/events/ChangeEventPlugin.js#L106
function getInstIfValueChanged(targetInst) {
const targetNode = getNodeFromInstance(targetInst);
if (inputValueTracking.updateValueIfChanged(targetNode)) {
return targetInst;
}
}
// packages/react-dom/src/client/ReactDOMComponentTree.js#L69
export function getNodeFromInstance(inst) {
if (inst.tag === HostComponent || inst.tag === HostText) {
// In Fiber this, is just the state node right now. We assume it will be
// a host component or host text.
return inst.stateNode;
}
// Without this first invariant, passing a non-DOM-component triggers the next
// invariant for a missing parent, which is super confusing.
invariant(false, 'getNodeFromInstance: Invalid argument.');
}
进入 shouldUseClickEvent
/**
* SECTION: handle `click` event
*/
// checkbox 和 radio 的特殊处理
function shouldUseClickEvent(elem) {
// Use the `click` event to detect changes to checkbox and radio inputs.
// This approach works across all browsers, whereas `change` does not fire
// until `blur` in IE8.
const nodeName = elem.nodeName;
return (
nodeName &&
nodeName.toLowerCase() === 'input' &&
(elem.type === 'checkbox' || elem.type === 'radio')
);
}
进入 getTargetInstForClickEvent
function getTargetInstForClickEvent(topLevelType, targetInst) {
if (topLevelType === TOP_CLICK) {
return getInstIfValueChanged(targetInst);
}
}
进入 createAndAccumulateChangeEvent
创建事件对象
function createAndAccumulateChangeEvent(inst, nativeEvent, target) {
const event = SyntheticEvent.getPooled(
eventTypes.change,
inst,
nativeEvent,
target,
);
event.type = 'change';
// Flag this event loop as needing state restore.
enqueueStateRestore(target);
accumulateTwoPhaseDispatches(event);
return event;
}
SyntheticEvent.getPooled
,就是说在react当中所有的事件对象是通过一个 pool 来进行一个存储的enqueueStateRestore
和 accumulateTwoPhaseDispatches
SyntheticEvent.getPooled
// packages/events/SyntheticEvent.js#L335
function addEventPoolingTo(EventConstructor) {
EventConstructor.eventPool = [];
EventConstructor.getPooled = getPooledEvent; // 注意这里
EventConstructor.release = releasePooledEvent;
}
getPooledEvent
// packages/events/SyntheticEvent.js#L300
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
const EventConstructor = this;
// 存在poll
if (EventConstructor.eventPool.length) {
const instance = EventConstructor.eventPool.pop();
EventConstructor.call(
instance,
dispatchConfig,
targetInst,
nativeEvent,
nativeInst,
);
return instance;
}
// pool 里面没有,则创建一个新的
return new EventConstructor(
dispatchConfig,
targetInst,
nativeEvent,
nativeInst,
);
}
EventConstructor
SyntheticEvent
构造方法SyntheticEvent
EventConstructor.call
和 new EventConstructor
都达到同一个目的enqueueStateRestore
export function enqueueStateRestore(target: EventTarget): void {
// 判断了这个 restoreTarget 公共变量是否存在
if (restoreTarget) {
if (restoreQueue) {
restoreQueue.push(target);
} else {
restoreQueue = [target];
}
// restoreTarget 不存在,则对其进行赋值
} else {
restoreTarget = target;
}
}
accumulateTwoPhaseDispatches
这个方法才是真正要去从每个节点上面去获取它的 listener 的一个过程// packages/events/EventPropagators.js#L115
export function accumulateTwoPhaseDispatches(events) {
// 其实就是对 events 这个数组里面,它的每一个节点去调用这个方法
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}
function accumulateTwoPhaseDispatchesSingle(event) {
// 存在 phasedRegistrationNames 则调用 traverseTwoPhase
if (event && event.dispatchConfig.phasedRegistrationNames) {
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
}
}
// 在 event 对象上 插入 listener 的过程
function accumulateDirectionalDispatches(inst, phase, event) {
// 忽略
if (__DEV__) {
warningWithoutStack(inst, 'Dispatching inst must not be null');
}
// 获取 listener
const listener = listenerAtPhase(inst, event, phase);
if (listener) {
// 注意这里 event._dispatchListeners 和 下面的 event._dispatchInstances 保持两者一一对应的关系
event._dispatchListeners = accumulateInto(
event._dispatchListeners,
listener,
);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
const registrationName =
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(inst, registrationName);
}
// packages/events/EventPluginHub.js#L126
export function getListener(inst: Fiber, registrationName: string) {
let listener;
// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
// live here; needs to be moved to a better place soon
const stateNode = inst.stateNode;
if (!stateNode) {
// Work in progress (ex: onload events in incremental mode).
return null;
}
const props = getFiberCurrentPropsFromNode(stateNode); // 从 dom tree上获取 props
if (!props) {
// Work in progress.
return null;
}
listener = props[registrationName];
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
return null;
}
invariant(
!listener || typeof listener === 'function',
'Expected `%s` listener to be a function, instead got a value of `%s` type.',
registrationName,
typeof listener,
);
return listener;
}
// packages/shared/ReactTreeTraversal.js#L86
export function traverseTwoPhase(inst, fn, arg) {
const path = [];
// 找到所有上层节点,并存入 path
while (inst) {
path.push(inst);
inst = getParent(inst);
}
// 下面是核心,执行两个阶段的回调,捕获和冒泡
let i;
for (i = path.length; i-- > 0; ) { // 注意这个 i 的顺序
// path[i]:节点, arg:event
fn(path[i], 'captured', arg); // captured 是从 window 向下触发的
}
for (i = 0; i < path.length; i++) { // 注意这个 i 的顺序
fn(path[i], 'bubbled', arg); // bubbled 是从下向 window 方向的
}
// 基于上面两个 循环
// 这样的话,就不需要在 event 对象上面单独维护 capture 的这个事件的它的一个数组
// 还有 bubble 的事件的一个速度,只需要放在同一个数组里面,然后按照这个数组的顺序去触发就可以了
}
// packages/shared/ReactTreeTraversal.js#L10
function getParent(inst) {
do {
inst = inst.return;
// TODO: If this is a HostRoot we might want to bail out.
// That is depending on if we want nested subtrees (layers) to bubble
// events to their parent. We could also go through parentNode on the
// host node but that wouldn't work for React Native and doesn't let us
// do the portal feature.
} while (inst && inst.tag !== HostComponent);
if (inst) {
return inst; // 返回的 inst 是一个 HostComponent
}
return null;
}
以上,生产 event 对象,然后去挂载它的事件,这个过程是非常的复杂的
react 团队把整个事件系统去重新抽象的这么一个过程,而且设计的超级复杂
这一套东西只是非常适合react,在其他框架要使用这类event库,会有很大的成本
目前为止,通过 ChangeEventPlugin 来了解了整个 event 对象的处理过程
后续其他的类似事件的处理逻辑到后面都是一样的
但每一个 plugin 或多或少有一些自己的一些区别,这里不再赘述