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節點執行
// 工做時不停的更新