React 为控制UI的更新,提出了时间分片的概念,以达到三个目标:performing non-blocking rendering;applying updates based on the priority;pre-rendering content in the background.javascript
React 遍历Dom Tree:java
1. updates state and propsnode
2. calls lifecycle hooksreact
3. retrieves the children from the component算法
4. compares them to the previous children api
5. figures out the dom updates the need to be performed浏览器
when dealing with UIs, the problem is that if too much work is executed all ai once ,it can cause animations to drap frames.数据结构
if react id going to walk the entire tree of components synchromously and perform work for each component ,it may run over 16ms available for an application code to execute its logic ,And this will cause frames to drop causing stuttering visual effects.app
在理解react如何经过时间分片的来实现对用户而言的无阻塞渲染以前,须要了解两个浏览器的api:dom
requestIdleCallback会在某一帧结束后的空余时间或者用户处于不活跃的的状态时,处理咱们的工做。利用这个API能使咱们尽量高效利用任何空闲的资源。
requestAnimationFrame 是浏览器用于定时循环操做的一个接口,相似于setTimeout,主要用途是按帧对网页进行重绘。设置这个API的目的是为了让各类网页动画(
Dom动画,Canvas动画,SVG动画,WebGL动画)可以有一个统一的刷新机制。显示器固有的刷新频率(60HZ/75HZ),requestAnimationFrame的基本思想就是与这个刷新频率保持一致。
requestIdleCallback((deadline)=>{ // 可用时间 & 是否有可用时间
console.log(deadline.timeRemaining(),deadline.didTimeOut) }) //timieRemaining can change as soon as browser gets some work to do,so it should be constanly checked。
要知道浏览器渲染一帧后的剩余时间,除了浏览器自己,利用js基本很难准确计算获得,由于当requestAnimationFrame的回调完成后,还要进行样式的计算,布局,渲染以及浏览器内部的工做等,还要确保当前没有用户交互。
当事件不少时,你可能会担忧你的回调函数永远不会被执行。requestIdleCallback有一个可选的第二参数:含有timeout属性的对象。若是设置了这个timeout的值,回调函数尚未调用的话,则浏览器必须在设置的这个毫秒数后,去强制调用对应的回调函数。若是回调函数由timeout触发,timeRemaining()会返回0,deadtime对象的didTimeout属性值true。(设置timeout会破坏潜规则)
若是在某一帧的末尾,回调函数被触发,它将被安排在当前帧被Commit以后,这表示相应的样式已经改动,同时更重要的是布局已经从新计算。日过咱们在这个回调中进行样式的改动,设计到布局的计算则会被判无效。若是在下一帧中有任何读取布局相关的操做,浏览器不得不执行一次强制同步布局,这将是一个潜在的性能瓶颈。此外不要在回调函数中触发dom改动的缘由是DOM改动的时间是不可预期的,正由于如此,DOM操做很容易地超过浏览器给出的限期。
最佳实践是在requestAnimationFrame的回调中去进行dom改动,由于浏览器会优化同类型的改动。这意味能够在下一次的requestAnimationFrame回调中添加一个文档片断。若是用的是虚拟DOM的库,你可在requestIdleCallback中作改动,但要在下一次requestAnimationFrame中将这些改动应用到Dom上,而不是在背刺requestIdleCallback中。
为了使用这些API,须要把整个tree的渲染工做划分为可逐渐递增的单元。同时为了达到这个目标,React须要从新实现遍历树的算法,从以前的依赖内部调用栈的同步递归模型转向经过指针连接链表的异步模型。缘由在于若是依赖内部调用栈,遍历更新的工做会一直进行直到调用栈为空。React Filter的目标在于把一次连续的树的遍历操做变为可单步执行的栈的片断,以达到无阻塞渲染和优先级渲染的目的。
(调用堆栈是一种数据结构,用来存储有关计算机程序活跃子程序的信息,调用堆栈存在的只要缘由是跟踪每一个活跃的子程序在完成执行时应该返回的控制位置。)
递归的算法很是直观,合适遍历树,可是它是有局限性的,最大的一点就是没法分解工做为增量单元,这使得React不能暂停特定组件的工做并在稍后恢复。经过递归,React只能不断迭代直到它处理完成全部组件,而且堆栈为空。
React Fiber采用邻接链表树遍历算法来代替递归遍历。
// 结构体
class Node{
constructor(instance){
this.instance = instance;
this.child = null; // fisrt child
this.sibling = null; // first sibling
this.return = null; // parent
}
}
function link(parent,elements){
if(elements === null) elements =[];
parent.child = elements.reduceRight((previous,current)=>{
const node = new Node(current);
node.return = parent;
node.sibling = previous;
return node;
},null)
return parent.child
}
// the function iterates over the array of nodes starting from the last one and links them //together in a singly linked list. It returns the reference
// to the first sibling in the list
function doWork(node){
const children = node.instance.render()
return link(node,children)
}
//it's parent first,depth-first implementation
function walk(o){
let root = o;
let current = o;
while(true){
let child = doWork(current);
if(child){
current = child
continue;
}
if(current === root){
return ;
}
while(!current.sibling){
if(!current.return || current.return === root){
return;
}
current = current.return;
}
}
}
// Fiber is re-implementation of the stack,specialized for React components.You can think of a //single fiber as a virtual stackframe
// we can stop the traversal at any time and resume to it later.That's exactly the condition we //wanted to achieve to be able to use the new
// requestIdleCallback API
function workLoop(isYieldy){
if(!isYieldy){
while(nextUnitOfWork !== null){
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
}else{
while(nextUnitOfWork !== null && !shouldYiels()){
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
}
}
// nextUnitOfWork 变量做为顶部帧,保留对当前Fiber节点的引用。函数shouldYield返回基于//deadlineDidExpire和deadline变量的结果,这些变量在React为Fiber节点执行
// 工做时不停的更新