React 处理事件的方法是事件委托。无论子元素有多少相同事件,在doc上只挂载一次,且挂载的是一个 dispatch 方法。
事件概览
先来看下react 的事件系统.
这是React Event源码中介绍的插件,具体的插件和上图有些差异。event 在不同端实现的方式不同。
import BeforeInputEventPlugin from '../events/BeforeInputEventPlugin';
import ChangeEventPlugin from '../events/ChangeEventPlugin';
import DOMEventPluginOrder from '../events/DOMEventPluginOrder';
import EnterLeaveEventPlugin from '../events/EnterLeaveEventPlugin';
import SelectEventPlugin from '../events/SelectEventPlugin';
import SimpleEventPlugin from '../events/SimpleEventPlugin';
//插件是React-dom/events 实现部分功能。
EventPluginHubInjection.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin,//beforeInput 是规范,但没有在任何浏览器中实现
});
在初始React-dom时,会注入写 DOMEventPlugin,这些插件注入是有一定顺序的。具体如下:
//packages\react-dom\src\events\DOMEventPluginOrder.js
const DOMEventPluginOrder = [
'ResponderEventPlugin',
'SimpleEventPlugin',
'EnterLeaveEventPlugin',
'ChangeEventPlugin',
'SelectEventPlugin',
'BeforeInputEventPlugin',
];
export default DOMEventPluginOrder;
react中的事件机制是按w3c实现的,有些浏览器不支持的事件,react是支持的。例如“beforeInput”是规范的事件,但没有在任何浏览器中实现。在react中之所以可不考虑事件的兼容性,并且可以按照w3c中的规范进行操作,其原因就是这些插件。
扩展event方法
在解析 react-dom.js文件时会修改event的方法,例如对preventDefault、stopPropagation的修改。
_assign(SyntheticEvent.prototype, {
preventDefault: function preventDefault() {
this.defaultPrevented = true;
var event = this.nativeEvent;
if (!event) { return; }
if (event.preventDefault) {
event.preventDefault();
} else if (typeof event.returnValue !== 'unknown') {
event.returnValue = false;
}
this.isDefaultPrevented = functionThatReturnsTrue;
},
stopPropagation: function stopPropagation() {
var event = this.nativeEvent;
if (!event) {
return;
}
if (event.stopPropagation) {
event.stopPropagation();
} else if (typeof event.cancelBubble !== 'unknown') {
// IE specific
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
},
persist: function persist() {
this.isPersistent = functionThatReturnsTrue;
}
//...省略代码
});
function functionThatReturnsTrue() {
return true;
}
function functionThatReturnsFalse() {
return false;
}
触发事件
class App extends Component {
render() {
return <div onClick={ ()=>{ console.log('被点击了') }}>Hello World!</div>
}
}
运行例子,并点击div触发onClick,其调用栈如下:
在绑定事件 trapBubbledEvent 绑定事件时,绑定的监听方法是 dispatchInteractiveEvent 和 dispatchEvent中的一个。dispatchInteractiveEvent 间接调用的是dispatchEvent。
点击click触发 dispatch 事件,此处派发的方法是 dispatchInteractiveEvent ,其调用方法如下:
function dispatchInteractiveEvent(topLevelType, nativeEvent) {
interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}
function interactiveUpdates(fn, a, b) {
return _interactiveUpdatesImpl(fn, a, b);
}
在 interactiveUpdates$1 开启批处理:isBatchingUpdates=true。
function interactiveUpdates$1(fn, a, b) {
if (!isBatchingUpdates && !isRendering &&
lowestPriorityPendingInteractiveExpirationTime !== NoWork) {
// Synchronously flush pending interactive updates.
performWork(lowestPriorityPendingInteractiveExpirationTime, false);
lowestPriorityPendingInteractiveExpirationTime = NoWork;
}
var previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
//unstable标志着这些代码不稳定,可能被修改。
return scheduler.unstable_runWithPriority(
scheduler.unstable_UserBlockingPriority,
function () { return fn(a, b); }//执行事件方法
);
} finally {
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork(); //开始更新
}
}
}
react是函数编程,在函数调用时,将相应功能组合。在 interactiveUpdates$1 中可知道,执行完事件方法才会调用performSyncWork。
dispatchEvent
由 dispatchInteractiveEvent 中可以知道 scheduler.unstable_runWithPriority中 fn 是dispatchEvent,a是topLevelType,b是nativeEvent。
function dispatchEvent(topLevelType, nativeEvent) {
if (!_enabled) { return; }
var nativeEventTarget = getEventTarget(nativeEvent);
var targetInst = getClosestInstanceFromNode(nativeEventTarget);
if (targetInst !== null && typeof targetInst.tag === 'number'
&& !isFiberMounted(targetInst)) {
targetInst = null;
}
var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);
try {
// 在同一个循环中处理事件队列允许使用“preventDefault”。
batchedUpdates(handleTopLevel, bookKeeping);
} finally {
releaseTopLevelCallbackBookKeeping(bookKeeping);
}
}
var CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
var callbackBookkeepingPool = [];
//callbackBookkeepingPool.pop;
function getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
if (callbackBookkeepingPool.length) {
var instance = callbackBookkeepingPool.pop();//
instance.topLevelType = topLevelType;
instance.nativeEvent = nativeEvent;
instance.targetInst = targetInst;
return instance;
}
return {
topLevelType: topLevelType,
nativeEvent: nativeEvent,
targetInst: targetInst,
ancestors: []
};
}
// callbackBookkeepingPool.push
function releaseTopLevelCallbackBookKeeping(instance) {
instance.topLevelType = null;
instance.nativeEvent = null;
instance.targetInst = null;
instance.ancestors.length = 0;
if (callbackBookkeepingPool.length < CALLBACK_BOOKKEEPING_POOL_SIZE) {
callbackBookkeepingPool.push(instance);
}
}
batchedUpdates
由上面可知道fn是handleTopLevel;_batchedUpdatesImpl 是batchedUpdates$1,编译命名的问题(忽略)。
var isBatching = false;
function batchedUpdates(fn, bookkeeping) {//fn 是 handleTopLevel
if (isBatching) { //如果当前在另一个批处理中,则需要等到它完全完成后才能恢复状态
return fn(bookkeeping);
}
isBatching = true;
try {
return _batchedUpdatesImpl(fn, bookkeeping);
} finally {
isBatching = false;
var controlledComponentsHavePendingUpdates = needsStateRestore();
if (controlledComponentsHavePendingUpdates) {
_flushInteractiveUpdatesImpl();
restoreStateIfNeeded();
}
}
}
function batchedUpdates$1(fn, a) {
var previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
return fn(a);// 等于 handleTopLevel(bookKeeping)
} finally {
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {// 使用原生事件更新sate
performSyncWork();
}
}
}
function handleTopLevel(bookKeeping) {
var targetInst = bookKeeping.targetInst; //遍历层次结构,以防有嵌套组件
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++) {// ancestors-->上代
targetInst = bookKeeping.ancestors[i];
runExtractedEventsInBatch(bookKeeping.topLevelType, targetInst,
bookKeeping.nativeEvent,
getEventTarget(bookKeeping.nativeEvent));
}
}
//返回的是HostRoot
function findRootContainerNode(inst) {
while (inst.return) { inst = inst.return; }
if (inst.tag !== HostRoot) { return null; }
return inst.stateNode.containerInfo;
}
执行到此基本上是包装,为后续的执行设定条件。在handleTopLevel中执行runExtractedEventsInBatch。
runExtractedEventsInBatch
runExtractedEventsInBatch用于提取组件中的事件,形成捕获和冒泡队列。
function runExtractedEventsInBatch(topLevelType, targetInst,
nativeEvent, nativeEventTarget) {
var events = extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
runEventsInBatch(events);
}
extractEvents
plugins的值是[null, ‘SimpleEventPlugin’,’EnterLeaveEventPlugin’,’ChangeEventPlugin’,
‘SelectEventPlugin’, ‘BeforeInputEventPlugin’]。这些事件都有 extractEvents 接口,但仅 SimpleEventPlugin 返回extractedEvents值。
//packages\events\EventPluginHub.js
function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
var events = null;
for (var i = 0; i < plugins.length; i++) {// 并不是所有的插件都可以在运行时加载.
var possiblePlugin = plugins[i];
if (possiblePlugin) {
var extractedEvents = possiblePlugin.extractEvents(topLevelType,
targetInst, nativeEvent,
nativeEventTarget);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
return events;
}
在SimpleEventPlugin 会处理event的信息并收集事件信息.
var SimpleEventPlugin = {
extractEvents: function extractEvents(topLevelType, targetInst,
nativeEvent, nativeEventTarget) {
var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
if (!dispatchConfig) { return null; }
//....省略。
var event = EventConstructor.getPooled(dispatchConfig, targetInst,
nativeEvent, nativeEventTarget);
accumulateTwoPhaseDispatches(event);
return event;
}
}
function accumulateTwoPhaseDispatches(events) {
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);//forEachAccumulated 在后面
}
function accumulateTwoPhaseDispatchesSingle(event) {
if (event && event.dispatchConfig.phasedRegistrationNames) {
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
}
}
// fn 是 accumulateDirectionalDispatches;inst是实例
function traverseTwoPhase(inst, fn, arg) {
var path = [];
while (inst) {// 收集当前inst 和 父级 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);
}
}
traverseTwoPhase 中 path是当前dom到根节点的目录。traverseTwoPhase中的 fn 是 accumulateDirectionalDispatches,在for循环中依次执行 fn 收集 path 到dom上的事件,这和冒泡和捕获机制一样。
function accumulateDirectionalDispatches(inst, phase, event) {
const listener = listenerAtPhase(inst, event, phase);
if (listener) {
event._dispatchListeners = accumulateInto(event._dispatchListeners,listener);//此处事件数组
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);//此处实例数组
}
}
event._dispatchListeners 保存整棵树的事件。event._dispatchInstances 保存对应的dom 实例。
listenerAtPhase
event.dispatchConfig.phasedRegistrationNames 中存放当前事件名称,以onClick为例,此时它的值为 {bubbled: “onClick”, captured: “onClickCapture”}。在traverseTwoPhase中传入的phase值依次为captured、bubbled,就可收集 path(树) 上的onClickCapture、onClick 事件。
function listenerAtPhase(inst, event, propagationPhase) {
// registrationName 为事件的名称,例如 onClick
const registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(inst, registrationName);
}
//获得事件方法
export function getListener(inst: Fiber, registrationName: string) {
let listener;
const stateNode = inst.stateNode;
if (!stateNode) {
return null;
}
const props = getFiberCurrentPropsFromNode(stateNode);
if (!props) {// Work in progress.
return null;
}
listener = props[registrationName]; // 实例上的方法
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
return null;
}
return listener;
}
accumulateInto
traverseTwoPhase 是在for循环中执行accumulateDirectionalDispatches,也就是说 listenerAtPhase是逐级收集的。accumulateInto 会将传入的值转为数组,形成相应的队列并返回。
function accumulateInto(current, next) {
if (current == null) {
return next;
}
if (Array.isArray(current)) {
if (Array.isArray(next)) {
current.push.apply(current, next);
return current;
}
current.push(next);
return current;
}
if (Array.isArray(next)) {
return [current].concat(next);
}
return [current, next];
}
runEventsInBatch
开始运行批处理事件。
var eventQueue = null;
export function runEventsInBatch(events) {
if (events !== null) { eventQueue = accumulateInto(eventQueue, events); }
const processingEventQueue = eventQueue;
eventQueue = null;
if (!processingEventQueue) { return; }
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
invariant(
!eventQueue,
'processEventQueue(): Additional events were enqueued while processing ' +
'an event queue. Support for this has not yet been implemented.',
);
rethrowCaughtError();
}
forEachAccumulated
若传入是数组则遍历执行;若传入是方法则执行方法;
function forEachAccumulated(arr, cb, scope) {
if (Array.isArray(arr)) {
arr.forEach(cb, scope);
} else if (arr) {
cb.call(scope, arr);
}
}
executeDispatchesAndReleaseTopLevel
executeDispatchesInOrder按队列执行方法,先执行captured事件,接着执行bubbled事件。若其中一个事件stopPropagation则isPropagationStopped返回true,不再执行后续事件。
var executeDispatchesAndReleaseTopLevel = function executeDispatchesAndReleaseTopLevel(e) {
return executeDispatchesAndRelease(e);
};
var executeDispatchesAndRelease = function executeDispatchesAndRelease(event) {
if (event) {
executeDispatchesInOrder(event);
if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
function executeDispatchesInOrder(event) {
var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;//包括captured 和 bubbled
if (Array.isArray(dispatchListeners)) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {//是否终止冒泡
break;
}
//Listeners 和 Instances是两个总是同步的并行数组
executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
}
} else if (dispatchListeners) {
executeDispatch(event, dispatchListeners, dispatchInstances);
}
event._dispatchListeners = null;
event._dispatchInstances = null;
}
function executeDispatch(event, listener, inst) {
const type = event.type || 'unknown-event';
event.currentTarget = getNodeFromInstance(inst);// 当前事件中的实例
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);//执行方法
event.currentTarget = null;
}
function getNodeFromInstance$1(inst) {
if (inst.tag === HostComponent || inst.tag === HostText) {
return inst.stateNode;
}
invariant(false, 'getNodeFromInstance: Invalid argument.');
}
执行调用listener
在执行事件时需要 捕获异常 和处理异常,invokeGuardedCallbackAndCatchFirstError 就是创建这些环节的方法。
function invokeGuardedCallbackAndCatchFirstError(name, func, context, a, b, c, d, e, f) {
invokeGuardedCallback.apply(this, arguments);
if (hasError) {
var error = clearCaughtError();
if (!hasRethrowError) { hasRethrowError = true; rethrowError = error; }
}
}
var reporter = {
onError: function onError(error) { hasError = true; caughtError = error; }
};
// context 是undefined
function invokeGuardedCallback(name, func, context, a, b, c, d, e, f) {
hasError = false;
caughtError = null;
invokeGuardedCallbackImpl$1.apply(reporter, arguments);
}
// funcArgs是 listener
var invokeGuardedCallbackImpl = function invokeGuardedCallbackImpl(name, func, context,
a, b, c, d, e, f) {
var funcArgs = Array.prototype.slice.call(arguments, 3);
try {
func.apply(context, funcArgs);//执行dom上的事件
} catch (error) {
this.onError(error);//this => reporter
}
};
批处理
处理 原生事件和 react 事件 是不一样的,react是批处理事件。例如,在react组件中使用setState设置state,代码如下:
class App extends Component {
state = { val: 0 }
batchUpdates = () => {
this.setState({ val: this.state.val + 1 });console.log(this.state.val);
this.setState({ val: this.state.val + 1 });console.log(this.state.val);
this.setState({ val: this.state.val + 1 });console.log(this.state.val);
//this.state.val的值为多少呢?
}
render() {
return (<div onClick={this.batchUpdates}>{`Counter is ${this.state.val}`} </div>)
}
}
此时的console.log(this.state.val)输出的为0,组件中的this.state.val为 1。更新过程如下:
先执行完事件方法,然后调用preformSyncWork进行reconciliation。