React 处理事件的方法是事件委托。无论子元素有多少相同事件,在doc上只挂载一次,且挂载的是一个 dispatch 方法。

事件概览

先来看下react 的事件系统.
EventSystem.jpg
这是React Event源码中介绍的插件,具体的插件和上图有些差异。event 在不同端实现的方式不同。

  1. import BeforeInputEventPlugin from '../events/BeforeInputEventPlugin';
  2. import ChangeEventPlugin from '../events/ChangeEventPlugin';
  3. import DOMEventPluginOrder from '../events/DOMEventPluginOrder';
  4. import EnterLeaveEventPlugin from '../events/EnterLeaveEventPlugin';
  5. import SelectEventPlugin from '../events/SelectEventPlugin';
  6. import SimpleEventPlugin from '../events/SimpleEventPlugin';
  7. //插件是React-dom/events 实现部分功能。
  8. EventPluginHubInjection.injectEventPluginsByName({
  9. SimpleEventPlugin: SimpleEventPlugin,
  10. EnterLeaveEventPlugin: EnterLeaveEventPlugin,
  11. ChangeEventPlugin: ChangeEventPlugin,
  12. SelectEventPlugin: SelectEventPlugin,
  13. BeforeInputEventPlugin: BeforeInputEventPlugin,//beforeInput 是规范,但没有在任何浏览器中实现
  14. });

在初始React-dom时,会注入写 DOMEventPlugin,这些插件注入是有一定顺序的。具体如下:

  1. //packages\react-dom\src\events\DOMEventPluginOrder.js
  2. const DOMEventPluginOrder = [
  3. 'ResponderEventPlugin',
  4. 'SimpleEventPlugin',
  5. 'EnterLeaveEventPlugin',
  6. 'ChangeEventPlugin',
  7. 'SelectEventPlugin',
  8. 'BeforeInputEventPlugin',
  9. ];
  10. export default DOMEventPluginOrder;

react中的事件机制是按w3c实现的,有些浏览器不支持的事件,react是支持的。例如“beforeInput”是规范的事件,但没有在任何浏览器中实现。在react中之所以可不考虑事件的兼容性,并且可以按照w3c中的规范进行操作,其原因就是这些插件。

扩展event方法

在解析 react-dom.js文件时会修改event的方法,例如对preventDefault、stopPropagation的修改。

  1. _assign(SyntheticEvent.prototype, {
  2. preventDefault: function preventDefault() {
  3. this.defaultPrevented = true;
  4. var event = this.nativeEvent;
  5. if (!event) { return; }
  6. if (event.preventDefault) {
  7. event.preventDefault();
  8. } else if (typeof event.returnValue !== 'unknown') {
  9. event.returnValue = false;
  10. }
  11. this.isDefaultPrevented = functionThatReturnsTrue;
  12. },
  13. stopPropagation: function stopPropagation() {
  14. var event = this.nativeEvent;
  15. if (!event) {
  16. return;
  17. }
  18. if (event.stopPropagation) {
  19. event.stopPropagation();
  20. } else if (typeof event.cancelBubble !== 'unknown') {
  21. // IE specific
  22. event.cancelBubble = true;
  23. }
  24. this.isPropagationStopped = functionThatReturnsTrue;
  25. },
  26. persist: function persist() {
  27. this.isPersistent = functionThatReturnsTrue;
  28. }
  29. //...省略代码
  30. });
  31. function functionThatReturnsTrue() {
  32. return true;
  33. }
  34. function functionThatReturnsFalse() {
  35. return false;
  36. }

当然初始还有一些其它的操作,此处不叙述。

触发事件

  1. class App extends Component {
  2. render() {
  3. return <div onClick={ ()=>{ console.log('被点击了') }}>Hello World!</div>
  4. }
  5. }

运行例子,并点击div触发onClick,其调用栈如下:
image.png

在绑定事件 trapBubbledEvent 绑定事件时,绑定的监听方法是 dispatchInteractiveEvent 和 dispatchEvent中的一个。dispatchInteractiveEvent 间接调用的是dispatchEvent。

点击click触发 dispatch 事件,此处派发的方法是 dispatchInteractiveEvent ,其调用方法如下:

  1. function dispatchInteractiveEvent(topLevelType, nativeEvent) {
  2. interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
  3. }
  4. function interactiveUpdates(fn, a, b) {
  5. return _interactiveUpdatesImpl(fn, a, b);
  6. }

在 interactiveUpdates$1 开启批处理:isBatchingUpdates=true。

  1. function interactiveUpdates$1(fn, a, b) {
  2. if (!isBatchingUpdates && !isRendering &&
  3. lowestPriorityPendingInteractiveExpirationTime !== NoWork) {
  4. // Synchronously flush pending interactive updates.
  5. performWork(lowestPriorityPendingInteractiveExpirationTime, false);
  6. lowestPriorityPendingInteractiveExpirationTime = NoWork;
  7. }
  8. var previousIsBatchingUpdates = isBatchingUpdates;
  9. isBatchingUpdates = true;
  10. try {
  11. //unstable标志着这些代码不稳定,可能被修改。
  12. return scheduler.unstable_runWithPriority(
  13. scheduler.unstable_UserBlockingPriority,
  14. function () { return fn(a, b); }//执行事件方法
  15. );
  16. } finally {
  17. isBatchingUpdates = previousIsBatchingUpdates;
  18. if (!isBatchingUpdates && !isRendering) {
  19. performSyncWork(); //开始更新
  20. }
  21. }
  22. }

react是函数编程,在函数调用时,将相应功能组合。在 interactiveUpdates$1 中可知道,执行完事件方法才会调用performSyncWork。

dispatchEvent

由 dispatchInteractiveEvent 中可以知道 scheduler.unstable_runWithPriority中 fn 是dispatchEvent,a是topLevelType,b是nativeEvent。

  1. function dispatchEvent(topLevelType, nativeEvent) {
  2. if (!_enabled) { return; }
  3. var nativeEventTarget = getEventTarget(nativeEvent);
  4. var targetInst = getClosestInstanceFromNode(nativeEventTarget);
  5. if (targetInst !== null && typeof targetInst.tag === 'number'
  6. && !isFiberMounted(targetInst)) {
  7. targetInst = null;
  8. }
  9. var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);
  10. try {
  11. // 在同一个循环中处理事件队列允许使用“preventDefault”。
  12. batchedUpdates(handleTopLevel, bookKeeping);
  13. } finally {
  14. releaseTopLevelCallbackBookKeeping(bookKeeping);
  15. }
  16. }
  17. var CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
  18. var callbackBookkeepingPool = [];
  19. //callbackBookkeepingPool.pop;
  20. function getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
  21. if (callbackBookkeepingPool.length) {
  22. var instance = callbackBookkeepingPool.pop();//
  23. instance.topLevelType = topLevelType;
  24. instance.nativeEvent = nativeEvent;
  25. instance.targetInst = targetInst;
  26. return instance;
  27. }
  28. return {
  29. topLevelType: topLevelType,
  30. nativeEvent: nativeEvent,
  31. targetInst: targetInst,
  32. ancestors: []
  33. };
  34. }
  35. // callbackBookkeepingPool.push
  36. function releaseTopLevelCallbackBookKeeping(instance) {
  37. instance.topLevelType = null;
  38. instance.nativeEvent = null;
  39. instance.targetInst = null;
  40. instance.ancestors.length = 0;
  41. if (callbackBookkeepingPool.length < CALLBACK_BOOKKEEPING_POOL_SIZE) {
  42. callbackBookkeepingPool.push(instance);
  43. }
  44. }

batchedUpdates

由上面可知道fn是handleTopLevel;_batchedUpdatesImpl 是batchedUpdates$1,编译命名的问题(忽略)。

  1. var isBatching = false;
  2. function batchedUpdates(fn, bookkeeping) {//fn 是 handleTopLevel
  3. if (isBatching) { //如果当前在另一个批处理中,则需要等到它完全完成后才能恢复状态
  4. return fn(bookkeeping);
  5. }
  6. isBatching = true;
  7. try {
  8. return _batchedUpdatesImpl(fn, bookkeeping);
  9. } finally {
  10. isBatching = false;
  11. var controlledComponentsHavePendingUpdates = needsStateRestore();
  12. if (controlledComponentsHavePendingUpdates) {
  13. _flushInteractiveUpdatesImpl();
  14. restoreStateIfNeeded();
  15. }
  16. }
  17. }
  18. function batchedUpdates$1(fn, a) {
  19. var previousIsBatchingUpdates = isBatchingUpdates;
  20. isBatchingUpdates = true;
  21. try {
  22. return fn(a);// 等于 handleTopLevel(bookKeeping)
  23. } finally {
  24. isBatchingUpdates = previousIsBatchingUpdates;
  25. if (!isBatchingUpdates && !isRendering) {// 使用原生事件更新sate
  26. performSyncWork();
  27. }
  28. }
  29. }
  30. function handleTopLevel(bookKeeping) {
  31. var targetInst = bookKeeping.targetInst; //遍历层次结构,以防有嵌套组件
  32. var ancestor = targetInst;
  33. do {
  34. if (!ancestor) {
  35. bookKeeping.ancestors.push(ancestor);
  36. break;
  37. }
  38. var root = findRootContainerNode(ancestor);
  39. if (!root) { break; }
  40. bookKeeping.ancestors.push(ancestor);
  41. ancestor = getClosestInstanceFromNode(root);
  42. } while (ancestor);
  43. for (var i = 0; i < bookKeeping.ancestors.length; i++) {// ancestors-->上代
  44. targetInst = bookKeeping.ancestors[i];
  45. runExtractedEventsInBatch(bookKeeping.topLevelType, targetInst,
  46. bookKeeping.nativeEvent,
  47. getEventTarget(bookKeeping.nativeEvent));
  48. }
  49. }
  50. //返回的是HostRoot
  51. function findRootContainerNode(inst) {
  52. while (inst.return) { inst = inst.return; }
  53. if (inst.tag !== HostRoot) { return null; }
  54. return inst.stateNode.containerInfo;
  55. }

执行到此基本上是包装,为后续的执行设定条件。在handleTopLevel中执行runExtractedEventsInBatch。

runExtractedEventsInBatch

runExtractedEventsInBatch用于提取组件中的事件,形成捕获和冒泡队列。

  1. function runExtractedEventsInBatch(topLevelType, targetInst,
  2. nativeEvent, nativeEventTarget) {
  3. var events = extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
  4. runEventsInBatch(events);
  5. }

extractEvents

plugins的值是[null, ‘SimpleEventPlugin’,’EnterLeaveEventPlugin’,’ChangeEventPlugin’,
‘SelectEventPlugin’, ‘BeforeInputEventPlugin’]。这些事件都有 extractEvents 接口,但仅 SimpleEventPlugin 返回extractedEvents值。

  1. //packages\events\EventPluginHub.js
  2. function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
  3. var events = null;
  4. for (var i = 0; i < plugins.length; i++) {// 并不是所有的插件都可以在运行时加载.
  5. var possiblePlugin = plugins[i];
  6. if (possiblePlugin) {
  7. var extractedEvents = possiblePlugin.extractEvents(topLevelType,
  8. targetInst, nativeEvent,
  9. nativeEventTarget);
  10. if (extractedEvents) {
  11. events = accumulateInto(events, extractedEvents);
  12. }
  13. }
  14. }
  15. return events;
  16. }

在SimpleEventPlugin 会处理event的信息并收集事件信息.

  1. var SimpleEventPlugin = {
  2. extractEvents: function extractEvents(topLevelType, targetInst,
  3. nativeEvent, nativeEventTarget) {
  4. var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
  5. if (!dispatchConfig) { return null; }
  6. //....省略。
  7. var event = EventConstructor.getPooled(dispatchConfig, targetInst,
  8. nativeEvent, nativeEventTarget);
  9. accumulateTwoPhaseDispatches(event);
  10. return event;
  11. }
  12. }
  13. function accumulateTwoPhaseDispatches(events) {
  14. forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);//forEachAccumulated 在后面
  15. }
  16. function accumulateTwoPhaseDispatchesSingle(event) {
  17. if (event && event.dispatchConfig.phasedRegistrationNames) {
  18. traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
  19. }
  20. }
  21. // fn 是 accumulateDirectionalDispatches;inst是实例
  22. function traverseTwoPhase(inst, fn, arg) {
  23. var path = [];
  24. while (inst) {// 收集当前inst 和 父级 inst
  25. path.push(inst);
  26. inst = getParent(inst);
  27. }
  28. var i = void 0;
  29. for (i = path.length; i-- > 0;) {
  30. fn(path[i], 'captured', arg);
  31. }
  32. for (i = 0; i < path.length; i++) {
  33. fn(path[i], 'bubbled', arg);
  34. }
  35. }

traverseTwoPhase 中 path是当前dom到根节点的目录。traverseTwoPhase中的 fn 是 accumulateDirectionalDispatches,在for循环中依次执行 fn 收集 path 到dom上的事件,这和冒泡和捕获机制一样。

  1. function accumulateDirectionalDispatches(inst, phase, event) {
  2. const listener = listenerAtPhase(inst, event, phase);
  3. if (listener) {
  4. event._dispatchListeners = accumulateInto(event._dispatchListeners,listener);//此处事件数组
  5. event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);//此处实例数组
  6. }
  7. }

event._dispatchListeners 保存整棵树的事件。event._dispatchInstances 保存对应的dom 实例。

listenerAtPhase

event.dispatchConfig.phasedRegistrationNames 中存放当前事件名称,以onClick为例,此时它的值为 {bubbled: “onClick”, captured: “onClickCapture”}。在traverseTwoPhase中传入的phase值依次为captured、bubbled,就可收集 path(树) 上的onClickCapture、onClick 事件。

  1. function listenerAtPhase(inst, event, propagationPhase) {
  2. // registrationName 为事件的名称,例如 onClick
  3. const registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
  4. return getListener(inst, registrationName);
  5. }
  6. //获得事件方法
  7. export function getListener(inst: Fiber, registrationName: string) {
  8. let listener;
  9. const stateNode = inst.stateNode;
  10. if (!stateNode) {
  11. return null;
  12. }
  13. const props = getFiberCurrentPropsFromNode(stateNode);
  14. if (!props) {// Work in progress.
  15. return null;
  16. }
  17. listener = props[registrationName]; // 实例上的方法
  18. if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
  19. return null;
  20. }
  21. return listener;
  22. }

accumulateInto

traverseTwoPhase 是在for循环中执行accumulateDirectionalDispatches,也就是说 listenerAtPhase是逐级收集的。accumulateInto 会将传入的值转为数组,形成相应的队列并返回。

  1. function accumulateInto(current, next) {
  2. if (current == null) {
  3. return next;
  4. }
  5. if (Array.isArray(current)) {
  6. if (Array.isArray(next)) {
  7. current.push.apply(current, next);
  8. return current;
  9. }
  10. current.push(next);
  11. return current;
  12. }
  13. if (Array.isArray(next)) {
  14. return [current].concat(next);
  15. }
  16. return [current, next];
  17. }

runEventsInBatch

开始运行批处理事件。

  1. var eventQueue = null;
  2. export function runEventsInBatch(events) {
  3. if (events !== null) { eventQueue = accumulateInto(eventQueue, events); }
  4. const processingEventQueue = eventQueue;
  5. eventQueue = null;
  6. if (!processingEventQueue) { return; }
  7. forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
  8. invariant(
  9. !eventQueue,
  10. 'processEventQueue(): Additional events were enqueued while processing ' +
  11. 'an event queue. Support for this has not yet been implemented.',
  12. );
  13. rethrowCaughtError();
  14. }

accumulateInto 将传入的值转为队列。

forEachAccumulated

若传入是数组则遍历执行;若传入是方法则执行方法;

  1. function forEachAccumulated(arr, cb, scope) {
  2. if (Array.isArray(arr)) {
  3. arr.forEach(cb, scope);
  4. } else if (arr) {
  5. cb.call(scope, arr);
  6. }
  7. }

executeDispatchesAndReleaseTopLevel

executeDispatchesInOrder按队列执行方法,先执行captured事件,接着执行bubbled事件。若其中一个事件stopPropagation则isPropagationStopped返回true,不再执行后续事件。

  1. var executeDispatchesAndReleaseTopLevel = function executeDispatchesAndReleaseTopLevel(e) {
  2. return executeDispatchesAndRelease(e);
  3. };
  4. var executeDispatchesAndRelease = function executeDispatchesAndRelease(event) {
  5. if (event) {
  6. executeDispatchesInOrder(event);
  7. if (!event.isPersistent()) {
  8. event.constructor.release(event);
  9. }
  10. }
  11. };
  12. function executeDispatchesInOrder(event) {
  13. var dispatchListeners = event._dispatchListeners;
  14. var dispatchInstances = event._dispatchInstances;//包括captured 和 bubbled
  15. if (Array.isArray(dispatchListeners)) {
  16. for (var i = 0; i < dispatchListeners.length; i++) {
  17. if (event.isPropagationStopped()) {//是否终止冒泡
  18. break;
  19. }
  20. //Listeners 和 Instances是两个总是同步的并行数组
  21. executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
  22. }
  23. } else if (dispatchListeners) {
  24. executeDispatch(event, dispatchListeners, dispatchInstances);
  25. }
  26. event._dispatchListeners = null;
  27. event._dispatchInstances = null;
  28. }
  29. function executeDispatch(event, listener, inst) {
  30. const type = event.type || 'unknown-event';
  31. event.currentTarget = getNodeFromInstance(inst);// 当前事件中的实例
  32. invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);//执行方法
  33. event.currentTarget = null;
  34. }
  35. function getNodeFromInstance$1(inst) {
  36. if (inst.tag === HostComponent || inst.tag === HostText) {
  37. return inst.stateNode;
  38. }
  39. invariant(false, 'getNodeFromInstance: Invalid argument.');
  40. }

执行调用listener

在执行事件时需要 捕获异常 和处理异常,invokeGuardedCallbackAndCatchFirstError 就是创建这些环节的方法。

  1. function invokeGuardedCallbackAndCatchFirstError(name, func, context, a, b, c, d, e, f) {
  2. invokeGuardedCallback.apply(this, arguments);
  3. if (hasError) {
  4. var error = clearCaughtError();
  5. if (!hasRethrowError) { hasRethrowError = true; rethrowError = error; }
  6. }
  7. }
  8. var reporter = {
  9. onError: function onError(error) { hasError = true; caughtError = error; }
  10. };
  11. // context 是undefined
  12. function invokeGuardedCallback(name, func, context, a, b, c, d, e, f) {
  13. hasError = false;
  14. caughtError = null;
  15. invokeGuardedCallbackImpl$1.apply(reporter, arguments);
  16. }
  17. // funcArgs是 listener
  18. var invokeGuardedCallbackImpl = function invokeGuardedCallbackImpl(name, func, context,
  19. a, b, c, d, e, f) {
  20. var funcArgs = Array.prototype.slice.call(arguments, 3);
  21. try {
  22. func.apply(context, funcArgs);//执行dom上的事件
  23. } catch (error) {
  24. this.onError(error);//this => reporter
  25. }
  26. };

批处理

处理 原生事件和 react 事件 是不一样的,react是批处理事件。例如,在react组件中使用setState设置state,代码如下:

  1. class App extends Component {
  2. state = { val: 0 }
  3. batchUpdates = () => {
  4. this.setState({ val: this.state.val + 1 });console.log(this.state.val);
  5. this.setState({ val: this.state.val + 1 });console.log(this.state.val);
  6. this.setState({ val: this.state.val + 1 });console.log(this.state.val);
  7. //this.state.val的值为多少呢?
  8. }
  9. render() {
  10. return (<div onClick={this.batchUpdates}>{`Counter is ${this.state.val}`} </div>)
  11. }
  12. }

此时的console.log(this.state.val)输出的为0,组件中的this.state.val为 1。更新过程如下:
image.png
先执行完事件方法,然后调用preformSyncWork进行reconciliation。