React调度-概念篇

1,170 阅读4分钟
此文只为调度的概念篇,暂不涉及源码分析。

浏览器的时间片

        首先,你需要了解浏览器的重绘机制。一般的浏览器为经典的60hz,既一秒钟进行60次刷新。一帧的时间大概为:1000/60 约等于16.6ms。在每个16.6ms浏览器会对页面进行重绘。


        众所周知,JavaScript是单线程的,如果有一个很大的任务,这个任务所需要的时间远远大于16.6ms,在这个任务霸占主线程的时候,浏览器是无法响应任何其他任务。这个时候就会造成了页面卡死问题。

 
  render() {    let BLOCKDIVNUMBER = 100000;    let temp = [];    while (BLOCKDIVNUMBER) {      temp.push(        <div          style={{            display: "inline-block",            borderRadius: "1px solid black",            height: 200,            width: 200,            backgroundColor: "blue",            color: "#ffffff"          }}        >          {this.state.number}        </div>      );      BLOCKDIVNUMBER--;    }    return (      <div className="app">        <button onClick={this.click.bind(this)}>add</button>        {temp}        </div>    );  }

       如上代码,重绘这10万条数据需要5s。如果没有使用任务调度在这5s内浏览器的主线程完全被霸占,无法响应其他事件。

Fiber架构

        在Fiber Reconciler前,React使用的是Stack Reconciler,Stack Reconciler始终会一次性地同步处理整个Virtual DOM ,无法暂停,这也就是上文代码一次渲染10万条数据会导致页面卡死的原因。

        与之相比,Fiber Reconciler 重新实现了 React 的核心算法,整个 Virtual DOM 的更新任务拆分成一个个小的任务。每个小任务占用的很小,每次做完一个小任务之后,放弃一下自己的执行将主线程空闲出来,看看有没有其他的任务。如果有的话,就暂停本次任务,执行其他的任务,如果没有的话,就继续下一个任务。

任务调度

       在 React 16 改写的新 Fiber 架构帮助下,React 现在可以允许渲染过程分段完成,中间可以返回至主线程执行其他任务。现在重要的是理解,当启用这个模式之后,React 会把同步渲染的 React 组件切分成小块,然后在多个帧上运行。

       如上文提到的时间片概念,在60hz的刷新频率下,每16.6ms为一帧。而拆分的小任务正是在每帧的渲染完成后的idle时间段中执行。


 react调度目前的任务优先级有:

  1. Immediate 立即执行优先级,需要同步执行的任务。
  2. UserBlocking 用户阻塞型优先级(250 ms 后过期),需要作为用户交互结果运行的任务(例如,按钮点击)。
  3. Normal 普通优先级(5 s 后过期),不必让用户立即感受到的更新。
  4. Low 低优先级(10 s 后过期),可以推迟但最终仍然需要完成的任务(例如,分析通知)。
  5. Idle 空闲优先级(永不过期),不必运行的任务(例如,隐藏界面以外的内容)。

五种优先级任务对应的时间常数:  

var maxSigned31BitInt = 1073741823;

// Times out immediately
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
var USER_BLOCKING_PRIORITY = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
var IDLE_PRIORITY = maxSigned31BitInt;

      在引擎中,调度器将所有已经注册的回调函数按照过期时间(回调函数注册的时间加上该优先级的过期时间)排序然后存储在列表中。接着,调度器将自己注册在浏览器绘制下一帧之后的回调函数里。在这个回调函数中,浏览器将执行尽可能多的已注册回调函数,直到浏览开始绘制下一帧为止。

// 过期时间计算
expirationTime = window.performance.now() + taskExpirationTime

小结

      调度的核心概念可以概括为:

  1. 将庞大的DOM处理任务进行拆分
  2. 对拆分的任务进行优先级定义,计算过期时间。
  3. 使用requestAnimationFrame在每一帧的渲染任务完成后的时间片里执行优先级最高的任务。