React事件机制-事件分发

事件分发

之前讲述了事件如何绑定在document上,那么具体事件触发的时候是如何分发到具体的监听者呢?我们接着上次注册的事件代理看。当我点击update counter按钮时,触发注册的click事件代理。

function dispatchInteractiveEvent(topLevelType, nativeEvent) {
  interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}
function interactiveUpdates(fn, a, b) {
  return _interactiveUpdatesImpl(fn, a, b);
}
var _interactiveUpdatesImpl = function (fn, a, b) {
  return fn(a, b);
};

topLevelTypeclicknativeEvent为真实dom事件对象。看似很多,其实就做了一件事: 执行dispatchEvent(topLevelType, nativeEvent)。其实不然,_interactiveUpdatesImpl在后面被重新赋值为interactiveUpdates$1,完成了一次自我蜕变。

function setBatchingImplementation(batchedUpdatesImpl, interactiveUpdatesImpl, flushInteractiveUpdatesImpl) {
  _batchedUpdatesImpl = batchedUpdatesImpl;
  _interactiveUpdatesImpl = interactiveUpdatesImpl;
  _flushInteractiveUpdatesImpl = flushInteractiveUpdatesImpl;
}

function interactiveUpdates$1(fn, a, b) {
  if (!isBatchingUpdates && !isRendering && lowestPriorityPendingInteractiveExpirationTime !== NoWork) {
    performWork(lowestPriorityPendingInteractiveExpirationTime, false);
    lowestPriorityPendingInteractiveExpirationTime = NoWork;
  }
  var previousIsBatchingUpdates = isBatchingUpdates;
  isBatchingUpdates = true;
  try {
    return scheduler.unstable_runWithPriority(scheduler.unstable_UserBlockingPriority, function () {
      return fn(a, b);
    });
  } finally {
    isBatchingUpdates = previousIsBatchingUpdates;
    if (!isBatchingUpdates && !isRendering) {
      performSyncWork();
    }
  }
}

setBatchingImplementation(batchedUpdates$1, interactiveUpdates$1, flushInteractiveUpdates$1);

如果有任何等待的交互更新,条件满足的情况下会先同步更新,然后设置isBatchingUpdates,进行scheduler调度。最后同步更新。scheduler的各类优先级如下:

unstable_ImmediatePriority: 1
unstable_UserBlockingPriority: 2
unstable_NormalPriority: 3
unstable_LowPriority: 4
unstable_IdlePriority: 5

进入scheduler调度,根据优先级计算时间,开始执行传入的回调函数。然后调用dispatchEvent,最后更新immediate workflushImmediateWork里的调用关系很复杂,最终会调用requestAnimationFrame进行更新,这里不进行过多讨论。

function unstable_runWithPriority(priorityLevel, eventHandler) {
  switch (priorityLevel) {
    case ImmediatePriority:
    case UserBlockingPriority:
    case NormalPriority:
    case LowPriority:
    case IdlePriority:
      break;
    default:
      priorityLevel = NormalPriority;
  }

  var previousPriorityLevel = currentPriorityLevel;
  var previousEventStartTime = currentEventStartTime;
  currentPriorityLevel = priorityLevel;
  currentEventStartTime = exports.unstable_now();

  try {
    return eventHandler();
  } finally {
    currentPriorityLevel = previousPriorityLevel;
    currentEventStartTime = previousEventStartTime;
    flushImmediateWork();
  }
}

下面看看dispatchEvent的具体执行过程。

function dispatchEvent(topLevelType, nativeEvent) {
  if (!_enabled) {
    return;
  }
  // 获取事件触发的原始节点
  var nativeEventTarget = getEventTarget(nativeEvent);
  // 获取原始节点最近的fiber对象(通过缓存在dom上的internalInstanceKey属性来寻找),如果没找到会往父节点继续寻找。
  var targetInst = getClosestInstanceFromNode(nativeEventTarget);

  if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) {
    targetInst = null;
  }
  // 创建对象,包含事件名称,原始事件,目标fiber对象和ancestor(空数组);如果缓存池有则直接取出并根据参数初始化属性。
  var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);

  try {
    // 批处理事件
    batchedUpdates(handleTopLevel, bookKeeping);
  } finally {
    // 释放bookKeeping对象内存,并放入对象池缓存
    releaseTopLevelCallbackBookKeeping(bookKeeping);
  }
}

接着看batchedUpdates,其实就是设置isBatching变量然后调用handleTopLevel(bookkeeping)

function batchedUpdates(fn, bookkeeping) {
  if (isBatching) {
    return fn(bookkeeping);
  }
  isBatching = true;
  try {
    // _batchedUpdatesImpl其实指向batchedUpdates$1函数,具体细节这里不再赘述
    return _batchedUpdatesImpl(fn, bookkeeping);
  } finally {
    isBatching = false;
    var controlledComponentsHavePendingUpdates = needsStateRestore();
    if (controlledComponentsHavePendingUpdates) {
      _flushInteractiveUpdatesImpl();
      restoreStateIfNeeded();
    }
  }
}

所以将原始节点对应最近的fiber缓存在bookKeeping.ancestors中。

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++) {
    targetInst = bookKeeping.ancestors[i];
    runExtractedEventsInBatch(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));
  }
}

runExtractedEventsInBatch中调用了两个方法: extractEventsrunEventsInBatch。前者构造合成事件,后者批处理合成事件。

function runExtractedEventsInBatch(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
  var events = extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
  runEventsInBatch(events);
}

事件合成

 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;
}

plugins是所有合成事件集合的数组,EventPluginHub初始化的时候完成注入。遍历所有plugins,调用其extractEvents方法,返回构造的合成事件。accumulateInto函数则把合成事件放入events。本例click事件合适的pluginSimpleEventPlugin,其他plugin得到的extractedEvents都不满足if (extractedEvents)条件。

EventPluginHubInjection.injectEventPluginsByName({
  SimpleEventPlugin: SimpleEventPlugin,
  EnterLeaveEventPlugin: EnterLeaveEventPlugin,
  ChangeEventPlugin: ChangeEventPlugin,
  SelectEventPlugin: SelectEventPlugin,
  BeforeInputEventPlugin: BeforeInputEventPlugin,
});

接下来看看构造合成事件的具体过程,这里针对SimpleEventPlugin,其他plugin就不一一分析了,来看下其extractEvents:

extractEvents: function(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
    if (!dispatchConfig) {
      return null;
    }
    var EventConstructor = void 0;
    switch (topLevelType) {
      ...
      case TOP_CLICK:
      ...
        EventConstructor = SyntheticMouseEvent;
        break;
      ...
    }
    var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
    accumulateTwoPhaseDispatches(event);
    return event;
  }

topLevelEventsToDispatchConfig是一个map对象,存储着各类事件对应的配置信息。这里获取到click的配置信息,然后根据topLevelType选择对应的合成构造函数,这里为SyntheticMouseEvent。接着从SyntheticMouseEvent合成事件对象池中获取合成事件。调用EventConstructor.getPooled,最终调用的是getPooledEvent

注意: SyntheticEvent.extend方法中明确写有addEventPoolingTo(Class);所以,SyntheticMouseEvent有eventPool、getPooled和release属性。后面会详细介绍SyntheticEvent.extend

function addEventPoolingTo(EventConstructor) {
  EventConstructor.eventPool = [];
  EventConstructor.getPooled = getPooledEvent;
  EventConstructor.release = releasePooledEvent;
}

首次触发事件,对象池为空,所以这里需要新创建。如果不为空,则取出一个并初始化。

function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
  var EventConstructor = this;
  if (EventConstructor.eventPool.length) {
    var instance = EventConstructor.eventPool.pop();
    EventConstructor.call(instance, dispatchConfig, targetInst, nativeEvent, nativeInst);
    return instance;
  }
  return new EventConstructor(dispatchConfig, targetInst, nativeEvent, nativeInst);
}

合成事件的属性是由React主动生成的,一些属性和原生事件的属性名完全一致,使其完全符合W3C标准,因此在事件层面上具有跨浏览器兼容性。如果要访问原生对象,通过nativeEvent属性即可获取。这里SyntheticMouseEventSyntheticUIEvent扩展而来,而SyntheticUIEventSyntheticEvent扩展而来。

var SyntheticMouseEvent = SyntheticUIEvent.extend({
  ...
});

var SyntheticUIEvent = SyntheticEvent.extend({
  ...
});

SyntheticEvent.extend = function (Interface) {
  var Super = this;
  // 原型继承
  var E = function () {};
  E.prototype = Super.prototype;
  var prototype = new E();
  // 构造继承
  function Class() {
    return Super.apply(this, arguments);
  }
  _assign(prototype, Class.prototype);
  Class.prototype = prototype;
  Class.prototype.constructor = Class;

  Class.Interface = _assign({}, Super.Interface, Interface);
  Class.extend = Super.extend;
  addEventPoolingTo(Class);

  return Class;
};

当被new创建时,会调用父类SyntheticEvent进行构造。主要是将原生事件上的属性挂载到合成事件上,还配置了一些额外属性。

function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
  this.dispatchConfig = dispatchConfig;
  this._targetInst = targetInst;
  this.nativeEvent = nativeEvent;
  ...
}

合成事件构造完成后,调用accumulateTwoPhaseDispatches

function accumulateTwoPhaseDispatches(events) {
  forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}

// 循环处理所有的合成事件
function forEachAccumulated(arr, cb, scope) {
  if (Array.isArray(arr)) {
    arr.forEach(cb, scope);
  } else if (arr) {
    cb.call(scope, arr);
  }
}

// 检测事件是否具有捕获阶段和冒泡阶段
function accumulateTwoPhaseDispatchesSingle(event) {
  if (event && event.dispatchConfig.phasedRegistrationNames) {
    traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
  }
}

function traverseTwoPhase(inst, fn, arg) {
  var path = [];
  // 循环遍历当前元素及父元素,缓存至path
  while (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);
  }
}

function accumulateDirectionalDispatches(inst, phase, event) {
  // 获取当前阶段对应的事件处理函数
  var listener = listenerAtPhase(inst, event, phase);
  // 将相关listener和目标fiber挂载到event对应的属性上
  if (listener) {
    event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
    event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
  }
}

事件执行(批处理合成事件)

首先将events合并到事件队列,之前没有处理完毕的队列也一同合并。如果新的事件队列为空,则退出。反之开始循环处理事件队列中每一个eventforEachAccumulated前面有提到过,这里不再赘述。

function runEventsInBatch(events) {
  if (events !== null) {
    eventQueue = accumulateInto(eventQueue, events);
  }
  var processingEventQueue = eventQueue;
  eventQueue = null;

  if (!processingEventQueue) {
    return;
  }

  forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
  rethrowCaughtError();
}

接下来看看事件处理,executeDispatchesAndRelease方法将事件执行和事件清理分开。

var executeDispatchesAndReleaseTopLevel = function (e) {
  return executeDispatchesAndRelease(e);
};

var executeDispatchesAndRelease = function (event) {
  if (event) {
    // 执行事件
    executeDispatchesInOrder(event);

    if (!event.isPersistent()) {
      // 事件清理,将合成事件放入对象池
      event.constructor.release(event);
    }
  }
};

提取事件的处理函数和对应的fiber,调用executeDispatch

function executeDispatchesInOrder(event) {
  var dispatchListeners = event._dispatchListeners;
  var dispatchInstances = event._dispatchInstances;
  if (Array.isArray(dispatchListeners)) {
    for (var i = 0; i < dispatchListeners.length; i++) {
      if (event.isPropagationStopped()) {
        break;
      }
      executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
    }
  } else if (dispatchListeners) {
    executeDispatch(event, dispatchListeners, dispatchInstances);
  }
  event._dispatchListeners = null;
  event._dispatchInstances = null;
}

获取真实dom挂载到event对象上,然后开始执行事件。

function executeDispatch(event, listener, inst) {
  var type = event.type || 'unknown-event';
  // 获取真实dom
  event.currentTarget = getNodeFromInstance(inst);
  invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
  event.currentTarget = null;
}

invokeGuardedCallbackAndCatchFirstError下面调用的方法很多,最终会来到invokeGuardedCallbackImpl,关键就在func.apply(context, funcArgs);这里的func就是listener(本例中是handleClick),而funcArgs就是合成事件对象。至此,事件执行完毕。

var invokeGuardedCallbackImpl = function (name, func, context, a, b, c, d, e, f) {
  var funcArgs = Array.prototype.slice.call(arguments, 3);
  try {
    func.apply(context, funcArgs);
  } catch (error) {
    this.onError(error);
  }
};

事件清理

事件执行完之后,剩下就是一些清理操作。event.constructor.release(event)相当于releasePooledEvent(event)。由于click对应的是SyntheticMouseEvent,所以会放入SyntheticMouseEvent.eventPool中。EVENT_POOL_SIZE固定为10。

function releasePooledEvent(event) {
  var EventConstructor = this;
  event.destructor();
  if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
    EventConstructor.eventPool.push(event);
  }
}

这里做了两件事,第一手动释放event属性上的内存(将属性置为null),第二将event放入对象池。至此,清理工作完毕。

destructor: function () {
    ...
    this.dispatchConfig = null;
    this._targetInst = null;
    this.nativeEvent = null;
    this.isDefaultPrevented = functionThatReturnsFalse;
    this.isPropagationStopped = functionThatReturnsFalse;
    this._dispatchListeners = null;
    this._dispatchInstances = null;
    ...
}    

event清理完后,还会清理bookKeeping,同样也会放入对象池进行缓存。同样CALLBACK_BOOKKEEPING_POOL_SIZE也固定为10。

// callbackBookkeepingPool是react-dom中的全局变量
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);
  }
}

总结

最后执行performSyncWork。如果执行的事件内调用了this.setState,会进行reconciliationcommit。由于事件流的执行是批处理过程,同步调用this.setState不会立马更新,需等待所有事件执行完成,即scheduler调度完后才开始performSyncWork,最终才能拿到新的state。如果是setTimeout或者是在dom上另外addEventListener的回调函数中调用this.setState则会立马更新。因为执行回调函数的时候不经过React事件流。

原文地址:https://www.cnblogs.com/raion/p/10598473.html

时间: 2024-10-10 23:19:21

React事件机制-事件分发的相关文章

[JS]笔记12之事件机制--事件冒泡和捕获--事件监听--阻止事件传播

-->事件冒泡和捕获-->事件监听-->阻止事件传播 一.事件冒泡和捕获 1.概念:当给子元素和父元素定义了相同的事件,比如都定义了onclick事件,点击子元素时,父元素的onclick事件也会被触发.js里称这种事件连续发生的机制为事件冒泡或者事件捕获.IE浏览器:事件从里向外发生,事件从最精确对象(target)开始触发,然后到最不精确的对象(document)触发,即事件冒泡 Netscape:事件从外向里发生,事件从最不精确的对象(document)开始触发,然后到最精确对象(

DOM事件机制(事件捕获和事件冒泡和事件委托)

内容: 1.事件复习 2.事件冒泡与事件捕获 3.事件委托 1.事件复习 (1)事件 事件是用来处理响应的一个机制,这个响应可以来自于用户(点击, 鼠标移动, 滚动), 也可以来自于浏览器 下面的链接描述了所有事件:https://developer.mozilla.org/en-US/docs/Web/Events (2)事件绑定 事件绑定有3种方法,前两钟方法在这里不介绍,主要看第三种: addEventListener方法(使用事件监听绑定事件) addEventListener: 1 e

QT开发(六十三)——QT事件机制分析

QT开发(六十三)--QT事件机制分析 一.事件机制 事件是由系统或者QT平台本身在不同的时刻发出的.当用户按下鼠标.敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件在对用户操作做出响应时发出,如键盘事件等:另一些事件则是由系统自动发出,如计时器事件. 事件的出现,使得程序代码不会按照原始的线性顺序执行.线性顺序的程序设计风格不适合处理复杂的用户交互,如用户交互过程中,用户点击"打开文件"将开始执行打开文件的操作,用户点击"保存文件"将开始执

源码看React 事件机制

对React熟悉的同学都知道,React中的事件机制并不是原生的那一套,事件没有绑定在原生DOM上,发出的事件也是对原生事件的包装.那么这一切是怎么实现的呢? 事件注册 首先还是看我们熟悉的代码 <button onClick={this.autoFocus}>点击聚焦</button> 这是我们在React中绑定事件的常规写法.经由JSX解析,button会被当做组件挂载.而onClick这时候也只是一个普通的props.ReactDOMComponent在进行组件加载(moun

总结react native 事件机制

React 事件机制 一个组件的所有事件会使用统一的事件监听器,绑定到组件的最外层,那么如何使用? bind方法,绑定并且可以传递参数 <TouchableOpacity onPress={this.fun.bind(this)} onPress={this.fun.bind(this,"text")} > </TouchableOpacity> fun(){ } 如果不用传参数? 双冒号语法 {::this.fun} 在构造器内bind,好处是只绑定一次 co

事件分发,事件消费,事件传递机制

0.学习目的: 1.解决开发中点击冲突,滑动冲突 drawerLayout点击穿透 viewpager嵌套viewpager左右滑动的时候冲突 2.深入了解Android事件机制 1.事件 1.事件包括:点击,双击,长按,滑动,拖拽,多点触控,组成了Android的事件 2.事件动作组成: 1.ACTION_DOWN 按下事件, 所有的事件都是从按下开始的 2.ACTION_MOVE 移动事件 ,按下后移动 3.ACTION_UP 弹起事件, 手指离开 3.事件的传递过程 按下(屏幕) -- 系

Qt事件机制概览

Qt事件机制概览 Qt事件机制概览 消息循环 Qt事件循环 简介 QEventLoop 跨线程的信号和槽与事件循环 模态窗口 Native widget or Alien widget 创建Native widget 创建QApplication的message-only窗口 派发事件的公共基础方法 source code QApplication的创建过程 QWidget native QWidget 的创建过程 普通native widget回调过程 QApplication的message

舌尖上的安卓(android触控事件机制学习笔记录)

对于一个"我们从来不生产代码,我们只是大自然代码的搬运工"的码农来说.对android的触控机制一直是模棱两可的状态,特别是当要求一些自定义的控件和androide的自带控件(比如ViewPager,ListView,ScrollView)高度嵌套在一起使用时. 花了点时间梳理了下,做个笔记.对于一个触控的事件从用户输入到传递到Actigvity到最外层的Viewgroup在到子View,中间过程还可能穿插多个Viewgroup,android在ViewGroup提供了3个方法来控制流

Android事件机制之一:事件传递和消费

http://www.cnblogs.com/lwbqqyumidi/p/3500997.html 关于Android中的事件机制,用到的地方还是很多的,并且这个知识点还真有点复杂. 在写这篇文章前,网上看了不少博文,有的写的感觉挺不错的.只是当时感觉好像理解了,事后又很容易忘.现在自己也系统整理下吧. Android中的事件在表现形式上有很多,如onTach.onClick和onLongClick等,在具体微观上的表现形势有action_down.action_move和action_up等.