react render的原理及觸發(fā)時機說明
react render的原理及觸發(fā)時機

理解react的render函數,要從這三點來認識。原理、執(zhí)行時機、總結。
原理
在類組件和函數組件中,render函數的形式是不同的。
在類組件中render函數指的就是render方法;而在函數組件中,指的就是整個函數組件。
class?Foo?extends?React.Component?{
????render()?{ //類組件中
????????return?<h1>?Foo?</h1>;
????}
}function?Foo()?{? //函數組件中
????return?<h1>?Foo?</h1>;
}在render函數中的jsx語句會被編譯成我們熟悉的js代碼
在render過程中,React 將新調用的 render函數返回的樹與舊版本的樹進行比較,這一步是決定如何更新 DOM 的必要步驟,然后進行 diff 比較,更新 DOM樹
觸發(fā)時機
render的執(zhí)行時機主要分成了兩部分:
類組件調用 setState 修改狀態(tài):
class Foo extends React.Component {
? state = { count: 0 };
?
? increment = () => {
? ? const { count } = this.state;
?
? ? const newCount = count < 10 ? count + 1 : count;
?
? ? this.setState({ count: newCount });
? };
?
? render() {
? ? const { count } = this.state;
? ? console.log("Foo render");
?
? ? return (
? ? ? <div>
? ? ? ? <h1> {count} </h1>
? ? ? ? <button onClick={this.increment}>Increment</button>
? ? ? </div>
? ? );
? }
}函數組件通過useState hook修改狀態(tài):
function Foo() {
? const [count, setCount] = useState(0);
?
? function increment() {
? ? const newCount = count < 10 ? count + 1 : count;
? ? setCount(newCount);
? }
?
? console.log("Foo render");
??
? return (
? ? <div>
? ? ? <h1> {count} </h1>
? ? ? <button onClick={increment}>Increment</button>
? ? </div>
? );?
}函數組件通過useState這種形式更新數據,當數組的值不發(fā)生改變了,就不會觸發(fā)render
小結一下:
render函數里面可以編寫JSX,轉化成createElement這種形式,用于生成虛擬DOM,最終轉化成真實DOM
在React 中,類組件只要執(zhí)行了 setState 方法,就一定會觸發(fā) render 函數執(zhí)行,函數組件使用useState更改狀態(tài)不一定導致重新render
組件的props 改變了,不一定觸發(fā) render 函數的執(zhí)行,但是如果 props 的值來自于父組件或者祖先組件的 state
在這種情況下,父組件或者祖先組件的 state 發(fā)生了改變,就會導致子組件的重新渲染
所以,一旦執(zhí)行了setState就會執(zhí)行render方法,useState 會判斷當前值有無發(fā)生改變確定是否執(zhí)行render方法,一旦父組件發(fā)生渲染,子組件也會渲染
react Scheduler 原理
學習react也有一段時間了,最近零零碎碎看了些東西,總覺得改寫點東西沉淀下,聯系到react快速響應的理念,我覺得時間切片的使用是再出色不過了,時間切片的使用離不開scheduler,那就談談scheduler吧
scheduler是什么?
react16開始整個架構分成了三層,scheduler,Reconciler,renderer,因為為了實現將一個同步任務變成異步的可中斷的任務,react提出了fiber,因為最開始用的是stack,任務是無法中斷的,js執(zhí)行時間太長時會影響頁面的渲染造成卡頓,fiber中任務是可以終端,但是中斷的任務怎么連上,什么時間執(zhí)行,哪個先執(zhí)行,這都屬于是新的問題,因此scheduler出生了,以瀏覽器是否有剩余時間作為任務中斷的標準,那么我們需要一種機制,當瀏覽器有剩余時間時,scheduler會通知我們,同時scheduler會進行一系列的任務優(yōu)先級判斷,保證任務時間合理分配。
總結下scheduler的兩個功能:
- 時間切片
- 優(yōu)先級調度
時間切片
JS腳本執(zhí)行和瀏覽器布局、繪制不能同時執(zhí)行。在每16.6ms時間內,需要完成 JS腳本執(zhí)行 ----- 樣式布局 ----- 樣式繪制,當JS執(zhí)行時間過長,超出了16.6ms,這次刷新就沒有時間執(zhí)行樣式布局和樣式繪制了。
頁面掉幀,造成卡頓。時間切片是在瀏覽器每一幀的時間中,預留一些時間給JS線程,React利用這部分時間更新組件,預留的初始時間是5ms。
超過5ms,React將中斷js,等下一幀時間到來繼續(xù)執(zhí)行js。其實瀏覽器本身已經實現了時間切片的功能,這個API叫requestIdleCallback,requestIdleCallback 是 window 屬性上的方法,它的作用是在瀏覽器一幀的剩余空閑時間內執(zhí)行優(yōu)先度相對較低的任務。
但是由于requestIdleCallback 的這兩個缺陷,react決定自己模擬時間切片
- 1.瀏覽器兼容不好的問題
- 2.requestIdleCallback 的 FPS 只有 20,也就是 50ms 刷新一次,遠遠低于頁面流暢度的要求
回顧一個知識瀏覽器在16.6ms里面要做哪些事情
宏任務-- 微任務 -- requestAnimationFrame -- 瀏覽器重排/重繪 -- requestIdleCallback
我們可以一起來看下時間切片應該放在哪里,首先排除requestIdleCallback,缺點上文已經提到了,實際上時間切片是放在宏任務里面的,可以先說下為什么不放在其他地方的原因:
1.為什么不是微任務里面
微任務將在頁面更新前全部執(zhí)行完,所以達不到「將主線程還給瀏覽器」的目的。
2.為什么不使用requestAnimationFrame
如果第一次任務調度不是由 rAF() 觸發(fā)的,例如直接執(zhí)行 scheduler.scheduleTask(),那么在本次頁面更新前會執(zhí)行一次 rAF() 回調,該回調就是第二次任務調度。所以使用 rAF() 實現會導致在本次頁面更新前執(zhí)行了兩次任務。
為什么是兩次,而不是三次、四次?因為在 rAF() 的回調中再次調用 rAF(),會將第二次 rAF() 的回調放到下一幀前執(zhí)行,而不是在當前幀前執(zhí)行。
另一個原因是 rAF() 的觸發(fā)間隔時間不確定,如果瀏覽器間隔了 10ms 才更新頁面,那么這 10ms 就浪費了。(現有 WEB 技術中并沒有規(guī)定瀏覽器應該什么何時更新頁面,所以通常認為是在一次宏任務完成之后,瀏覽器自行判斷當前是否應該更新頁面。如果需要更新頁面,則執(zhí)行 rAF() 的回調并更新頁面。否則,就執(zhí)行下一個宏任務。)
3.既然是宏任務,那么是settimeout嗎?
遞歸執(zhí)行 setTimeout(fn, 0) 時,最后間隔時間會變成 4 毫秒,而不是最初的 1 毫秒,因為settimeout的執(zhí)行時機是和js執(zhí)行有關的,遞歸是會不準,最終使用 MessageChannel 產生宏任務,但是由于兼容,如果當前宿主環(huán)境不支持MessageChannel,則使用setTimeout。
在React的render階段,開啟Concurrent Mode時,每次遍歷前,都會通過Scheduler提供的shouldYield方法判斷是否需要中斷遍歷,使瀏覽器有時間渲染:
function workLoopConcurrent() {
? // Perform work until Scheduler asks us to yield
? while (workInProgress !== null && !shouldYield()) {
? ? performUnitOfWork(workInProgress);
? }
}是否中斷的依據,最重要的一點便是每個任務的剩余時間是否用完。
在Schdeduler中,為任務分配的初始剩余時間為5ms。如果shouldYield為true,任務就會中斷,中斷之后再次執(zhí)行就要用到調度了
任務調度
Scheduler對外暴露了一個方法unstable_runWithPriority,這個方法可以用來獲取優(yōu)先級
unction unstable_runWithPriority(priorityLevel, eventHandler) {
? switch (priorityLevel) {
? ? case ImmediatePriority:
? ? case UserBlockingPriority:
? ? case NormalPriority:
? ? case LowPriority:
? ? case IdlePriority:
? ? ? break;
? ? default:
? ? ? priorityLevel = NormalPriority;
? }
//。。。省略
}可以看到有5種優(yōu)先級,比如,我們知道commit階段是同步執(zhí)行的??梢钥吹剑琧ommit階段的起點commitRoot方法的優(yōu)先級為ImmediateSchedulerPriority。
ImmediateSchedulerPriority即ImmediatePriority的別名,為最高優(yōu)先級,會立即執(zhí)行??墒莾?yōu)先級只是一個名稱,react如何判斷優(yōu)先級的高低呢,這里我覺得和操作系統(tǒng)里面的一些概念還是挺相似的
給不同任務給上過期時間,誰快過期了就先執(zhí)行誰
var timeout;
switch (priorityLevel) {
? case ImmediatePriority:
? ? timeout = IMMEDIATE_PRIORITY_TIMEOUT;
? ? break;
? case UserBlockingPriority:
? ? timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
? ? break;
? case IdlePriority:
? ? timeout = IDLE_PRIORITY_TIMEOUT;
? ? break;
? case LowPriority:
? ? timeout = LOW_PRIORITY_TIMEOUT;
? ? break;
? case NormalPriority:
? default:
? ? timeout = NORMAL_PRIORITY_TIMEOUT;
? ? break;
}
var expirationTime = startTime + timeout;
// Times out immediately
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;可以看到 IMMEDIATE_PRIORITY_TIMEOUT =-1,說明比當前時間還早,已經過期,必須快執(zhí)行,初此之外,react新增了兩個隊列:已就緒任務 ,未就緒任務
所以,Scheduler存在兩個隊列:timerQueue:保存未就緒任務,taskQueue:保存已就緒任務
每當有新的未就緒的任務被注冊,我們將其插入timerQueue并根據開始時間重新排列timerQueue中任務的順序。當timerQueue中有任務就緒,即startTime <= currentTime,我們將其取出并加入taskQueue。
取出taskQueue中最早過期的任務并執(zhí)行他。
簡單介紹下scheduler的原理,其實要更多了解scheduler,還要再看看lane模型,這塊之后再說吧,還有fiber啥的,有時間再寫。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
在React中實現分塊導出大量數據表格并壓縮成圖片的解決方案
在現代Web開發(fā)中,處理和展示大量數據是一個常見的挑戰(zhàn),特別是在使用React框架時,我們經常需要將這些數據以表格的形式展示,并提供導出功能,本文將介紹如何在React中實現一個高效、分塊導出大量數據表格,并將其壓縮為圖片的解決方案,需要的朋友可以參考下2024-12-12
create-react-app全家桶router?mobx全局安裝配置
這篇文章主要為大家介紹了create-react-app全家桶router?mobx全局安裝配置,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
React如何使用refresh_token實現無感刷新頁面
本文主要介紹了React如何使用refresh_token實現無感刷新頁面,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-04-04
用react-redux實現react組件之間數據共享的方法
這篇文章主要介紹了用react-redux實現react組件之間數據共享的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06

