欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

React事件机制-事件分发

程序员文章站 2022-03-12 11:19:54
事件分发 之前讲述了事件如何绑定在 上,那么具体事件触发的时候是如何分发到具体的监听者呢?我们接着上次注册的事件代理看。当我点击 按钮时,触发注册的 事件代理。 为`click nativeEvent dispatchEvent(topLevelType, nativeEvent) _interac ......

事件分发

之前讲述了事件如何绑定在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事件流。