React Fiber的优先级调度机制与事件系统

更新日期: 2019-12-30阅读: 2.4k标签: 机制

经典的事件系统分成两大块,绑定事件与分派事件,在浏览器中,分派事件很少人会直接dispatchEvent。因为创建一个dom 事件是非常复杂的事情,不同的事件对象对应不同的事件构造器,传参也五花八门。因为分派事件基本上用户行为触发,比如我们点击了某个元素,恰逢在这上方绑定了点击事件,于是触发了。

react的绑定事件是在JSX 中进行,换言之, render时,props的onXXX事件就被收集起来,进行绑定。在jquery时,人们发明了事件委托,将可以冒泡的事件挂在document或window对象上,进行统一的监听,实现高性能的事件系统。现在我们可以无视IE8 ,因此对不可冒泡的事件可以使用事件捕获,对能冒泡的事件使用事件冒泡,也是放在顶层对象 上进行监听。

function trapBubbledEvent(topLevelType, element) {  //by 司徒正美
  trapEventForPluginEventSystem(element, topLevelType, false);
}
function trapCapturedEvent(topLevelType, element) {
  trapEventForPluginEventSystem(element, topLevelType, true);
}

大家可能觉得很奇怪,浏览器的dom.addEventListener(type, fn, capture) 怎么也要三个参数, 原因是fn是一个统一调度的方法,因此可省掉一个。而冒泡与捕获则通过不同的方法做区分,实际干活的是trapEventForPluginEventSystem。

trapEventForPluginEventSystem根据事件名进行分大三类,DiscreteEvent,UserBlockingEvent,ContinuousEvent,选出不同的事件派发器。

function trapEventForPluginEventSystem(element, topLevelType, capture) { //by 司徒正美
  var listener;

  switch (getEventPriority(topLevelType)) {
    case DiscreteEvent:
      listener = dispatchDiscreteEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
      break;

    case UserBlockingEvent:
      listener = dispatchUserBlockingUpdate.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
      break;

    case ContinuousEvent: //by 司徒正美
    default:
      listener = dispatchEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
      break;
  }

  var rawEventName = getRawEventName(topLevelType);

  if (capture) {
    addEventCaptureListener(element, rawEventName, listener);
  } else {
    addEventBubbleListener(element, rawEventName, listener);
  }
}

DiscreteEvent 离散事件. 例如blur、focus、 click、 submit、 touchStart. 这些事件都是离散触发的。

UserBlockingEvent 用户阻塞事件. 例如touchMove、mouseMove、scroll、drag、dragOver等等。这些事件会'阻塞'用户的交互。

ContinuousEvent 连续事件。例如load、error、loadStart、abort、animationEnd. 这个优先级最高,也就是说它们应该是立即同步执行的,这就是Continuous的意义,是持续地执行,不能被打断。

源码里有一个怒长的表,对所有常用事件进行归类

var eventTuples = [// Discrete events
[TOP_BLUR, 'blur', DiscreteEvent], [TOP_CANCEL, 'cancel', DiscreteEvent], [TOP_CLICK, 'click', DiscreteEvent], [TOP_CLOSE, 'close', DiscreteEvent], [TOP_CONTEXT_MENU, 'contextMenu', DiscreteEvent], [TOP_COPY, 'copy', DiscreteEvent], [TOP_CUT, 'cut', DiscreteEvent], [TOP_AUX_CLICK, 'auxClick', DiscreteEvent], [TOP_DOUBLE_CLICK, 'doubleClick', DiscreteEvent], [TOP_DRAG_END, 'dragEnd', DiscreteEvent], [TOP_DRAG_START, 'dragStart', DiscreteEvent], [TOP_DROP, 'drop', DiscreteEvent], [TOP_FOCUS, 'focus', DiscreteEvent], [TOP_INPUT, 'input', DiscreteEvent], [TOP_INVALID, 'invalid', DiscreteEvent], [TOP_KEY_DOWN, 'keyDown', DiscreteEvent], [TOP_KEY_PRESS, 'keyPress', DiscreteEvent], [TOP_KEY_UP, 'keyUp', DiscreteEvent], [TOP_MOUSE_DOWN, 'mouseDown', DiscreteEvent], [TOP_MOUSE_UP, 'mouseUp', DiscreteEvent], [TOP_PASTE, 'paste', DiscreteEvent], [TOP_PAUSE, 'pause', DiscreteEvent], [TOP_PLAY, 'play', DiscreteEvent], [TOP_POINTER_CANCEL, 'pointerCancel', DiscreteEvent], [TOP_POINTER_DOWN, 'pointerDown', DiscreteEvent], [TOP_POINTER_UP, 'pointerUp', DiscreteEvent], [TOP_RATE_CHANGE, 'rateChange', DiscreteEvent], [TOP_RESET, 'reset', DiscreteEvent], [TOP_SEEKED, 'seeked', DiscreteEvent], [TOP_SUBMIT, 'submit', DiscreteEvent], [TOP_TOUCH_CANCEL, 'touchCancel', DiscreteEvent], [TOP_TOUCH_END, 'touchEnd', DiscreteEvent], [TOP_TOUCH_START, 'touchStart', DiscreteEvent], [TOP_VOLUME_CHANGE, 'volumeChange', DiscreteEvent], // User-blocking events
[TOP_DRAG, 'drag', UserBlockingEvent], [TOP_DRAG_ENTER, 'dragEnter', UserBlockingEvent], [TOP_DRAG_EXIT, 'dragExit', UserBlockingEvent], [TOP_DRAG_LEAVE, 'dragLeave', UserBlockingEvent], [TOP_DRAG_OVER, 'dragOver', UserBlockingEvent], [TOP_MOUSE_MOVE, 'mouseMove', UserBlockingEvent], [TOP_MOUSE_OUT, 'mouseout', UserBlockingEvent], [TOP_MOUSE_OVER, 'mouseOver', UserBlockingEvent], [TOP_POINTER_MOVE, 'pointerMove', UserBlockingEvent], [TOP_POINTER_OUT, 'pointerOut', UserBlockingEvent], [TOP_POINTER_OVER, 'pointerOver', UserBlockingEvent], [TOP_SCROLL, 'scroll', UserBlockingEvent], [TOP_TOGGLE, 'toggle', UserBlockingEvent], [TOP_TOUCH_MOVE, 'touchMove', UserBlockingEvent], [TOP_WHEEL, 'wheel', UserBlockingEvent], // Continuous events
[TOP_ABORT, 'abort', ContinuousEvent], [TOP_ANIMATION_END, 'animationEnd', ContinuousEvent], [TOP_ANIMATION_ITERATION, 'animationIteration', ContinuousEvent], [TOP_ANIMATION_START, 'animationStart', ContinuousEvent], [TOP_CAN_PLAY, 'canPlay', ContinuousEvent], [TOP_CAN_PLAY_THROUGH, 'canPlayThrough', ContinuousEvent], [TOP_DURATION_CHANGE, 'durationChange', ContinuousEvent], [TOP_EMPTIED, 'emptied', ContinuousEvent], [TOP_ENCRYPTED, 'encrypted', ContinuousEvent], [TOP_ENDED, 'ended', ContinuousEvent], [TOP_ERROR, 'error', ContinuousEvent], [TOP_GOT_POINTER_CAPTURE, 'gotPointerCapture', ContinuousEvent], [TOP_LOAD, 'load', ContinuousEvent], [TOP_LOADED_DATA, 'loadedData', ContinuousEvent], [TOP_LOADED_METADATA, 'loadedMetadata', ContinuousEvent], [TOP_LOAD_START, 'loadStart', ContinuousEvent], [TOP_LOST_POINTER_CAPTURE, 'lostPointerCapture', ContinuousEvent], [TOP_PLAYING, 'playing', ContinuousEvent], [TOP_PROGRESS, 'progress', ContinuousEvent], [TOP_SEEKING, 'seeking', ContinuousEvent], [TOP_STALLED, 'stalled', ContinuousEvent], [TOP_SUSPEND, 'suspend', ContinuousEvent], [TOP_TIME_UPDATE, 'timeUpdate', ContinuousEvent], [TOP_TRANSITION_END, 'transitionEnd', ContinuousEvent], [TOP_WAITING, 'waiting', ContinuousEvent]];

addEventBubbleListener与addEventCaptureListener就是对DOM 的addEvenListener的简单封装。但要注意的是,这两个方法都是使用注入方式实现的。换言之,react-native中,由于打包的文件不一样,它对应的实现也不一样

function addEventBubbleListener(element, eventType, listener) {
  element.addEventListener(eventType, listener, false); //by 司徒正美
}
function addEventCaptureListener(element, eventType, listener) {
  element.addEventListener(eventType, listener, true); //by 司徒正美
}

然后我们终于有机会看dispatchDiscreteEvent,dispatchUserBlockingUpdate与dispatchEvent。

function dispatchDiscreteEvent(topLevelType, eventSystemFlags, nativeEvent) {
  flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp);
  discreteUpdates(dispatchEvent, topLevelType, eventSystemFlags, nativeEvent);
}
 //by 司徒正美
function dispatchUserBlockingUpdate(topLevelType, eventSystemFlags, nativeEvent) {
   runWithPriority(UserBlockingPriority, 
     dispatchEvent.bind(null, topLevelType, eventSystemFlags, nativeEvent));
}

派发离散事件分两部分,第一个是flushDiscreteUpdatesIfNeeded,你跟踪进去是flushDiscreteUpdates,它会搞定之前积攒的DiscreteEvent与useEffect回调。第二个是discreteUpdates,它会为React 的调度叠加一个DiscreteEventContext 上下文,并执行runWithPriority,这时看来它与dispatchUserBlockingUpdate无异,只是做了一个前置处理。

function discreteUpdates(fn, a, b, c) {
  var prevExecutionContext = executionContext;
  executionContext |= DiscreteEventContext;

  try {
    // Should this
    return runWithPriority(UserBlockingPriority, fn.bind(null, a, b, c));
  } finally {
    executionContext = prevExecutionContext;

    if (executionContext === NoContext) {
      // Flush the immediate callbacks that were scheduled during this batch
      flushSyncCallbackQueue();
    }
  }
}

runWithPriority对调度器的影响主要有两个,一个是fiber节点的expirationTime属性,涉及到fiber对DOM的刷新,另一个是fiber节点上的回调的执行(setState, forceUpdate, hooks),它们会转交scheduleCallback方法处理。scheduleCallback相当于一个setTimeout,根据priorityLevel,延迟执行事件。scheduleCallback详看这里:https://github.com/facebook/react/blob/master/packages/scheduler/src/Scheduler.js

priorityLevel能立即转换成callbackPriority,默认为NoPriority 90。然后运行过程中变成以下5种。

ImmediatePriority 99 IMMEDIATE_PRIORITY_TIMEOUT=-1;
UserBlockingPriority 98 USER_BLOCKING_PRIORITY=250
NormalPriority 97 NORMAL_PRIORITY_TIMEOUT=5000
LowPriority 96 LOW_PRIORITY_TIMEOUT=10000
IdlePriority 85 LOW_PRIORITY_TIMEOUT=10000

每一个fiber都分配一个expirationTime属性(其实有多种expirationTime属性),它是大于当时的毫秒数。但调度器执行时,就计算出当前的毫秒数now, 然后now - fiber.expirationTime <= 0,那么这fiber就可以更新了,其priorityLevel会改成ImmediatePriority。否则它只会创建一个effect记录用户的操作(如更新了某个属性啦,对它做删除啦)。

好了,我们知道runWithPriority重要性了,那么我们需要获取其他priorityLevel值。

我们会发现flushControlled,deferredUpdates,flushSync会包含 runWithPriority(NormalPriority, fn)逻辑。 syncUpdates, flushSyncCallback则包含runWithPriority(ImmediatePriority, fn)的逻辑。

此外,SuspenseComponent也会影响到expirationTime的计算(默认分配为LOW_PRIORITY_EXPIRATION),最后通过 内部 inferPriorityFromExpirationTime的方法计算priorityLevel值。

本文React版本为 16.10.2


总结: 

多年之前,人们说到fiber,只是模糊地联想到Time slicing与Suspense这两个单词。而经过多次迭代,我们终于能揭开fiber的真面目。 React的时间切片,只是它更新的一种表现,实质上是由每个fiber的expirationTime所决定,而fiber的expirationTime又来自priorityLevel,priorityLevel则来自用户的UI操作,不同的事件,带来三种不同的priorityLevel。而悬停,则只为某个fiber带来第四种priorityLevel——LowPriority。用户代码出现问题,被catch住时,出现第五种priorityLevel——IdlePriority。

作者:司徒正美
原文:https://zhuanlan.zhihu.com/p/95443185

链接: https://www.fly63.com/article/detial/7107

浅析前端页面渲染机制

作为一个前端开发,最常见的运行环境应该是浏览器吧,为了更好的通过浏览器把优秀的产品带给用户,也为了更好的发展自己的前端职业之路,有必要了解从我们在浏览器地址栏输入网址到看到页面这期间浏览器是如何进行工作的

这一次,彻底弄懂 JavaScript 执行机制

javascript是一门单线程语言,Event Loop是javascript的执行机制.牢牢把握两个基本点,以认真学习javascript为中心,早日实现成为前端高手的伟大梦想!

创建js hook钩子_js中的钩子机制与实现

钩子机制也叫hook机制,或者你可以把它理解成一种匹配机制,就是我们在代码中设置一些钩子,然后程序执行时自动去匹配这些钩子;这样做的好处就是提高了程序的执行效率,减少了if else 的使用同事优化代码结构

小程序的更新机制_如何实现强制更新?

在讲小程序的更新机制之前,我们需要先了解小程序的2种启动模式,分别为:冷启动和热启动。小程序不同的启动方式,对应的更新情况不不一样的。无论冷启动,还是热启动。小程序都不会马上更新的,如果我们需要强制更新,需要如何实现呢?

基于JWT的Token认证机制实现及安全问题

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其JWT的组成:一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

web前端-JavaScript的运行机制

本文介绍JavaScript运行机制,JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。

轮询机制解决后端任务回调问题

现在有一个需求,前端有一个按钮,点击以后会调用后端一个接口,这个接口会根据用户的筛选条件去hadoop上跑任务,将图片的base64转为img然后打包成zip,生成一个下载连接返回给前端,弹出下载框。hadoop上的这个任务耗时比较久

JavaScript预解释是一种毫无节操的机制

js代码执行之前,浏览器首先会默认的把所有带var和function的进行提前的声明或者定义:1.理解声明和定义、2.对于带var和function关键字的在预解释的时候操作不一样的、3.预解释只发生在当前的作用域下

js对代码解析机制

脚本执行js引擎都做了什么呢?1.语法分析 2.预编译 3.解释执行。在执行代码前,还有两个步骤;语法分析很简单,就是引擎检查你的代码有没有什么低级的语法错误 ,查找全局变量声明(包括隐式全局变量声明,省略var声明),变量名作全局对象的属性,值为undefined

web认证机制

以前对认证这方面的认识一直不太深刻,不清楚为什么需要token这种认证,为什么不简单使用session存储用户登录信息等。最近读了几篇大牛的博客才对认证机制方面有了进一步了解。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!