日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

react?render的原理及觸發時機說明_React

作者:嘻嘍嘍嘍 ? 更新時間: 2023-04-27 編程語言

react render的原理及觸發時機

理解react的render函數,要從這三點來認識。原理、執行時機、總結。

原理

在類組件和函數組件中,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樹?

觸發時機

render的執行時機主要分成了兩部分:

類組件調用 setState 修改狀態:

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修改狀態:

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這種形式更新數據,當數組的值不發生改變了,就不會觸發render

小結一下:

render函數里面可以編寫JSX,轉化成createElement這種形式,用于生成虛擬DOM,最終轉化成真實DOM

在React 中,類組件只要執行了 setState 方法,就一定會觸發 render 函數執行,函數組件使用useState更改狀態不一定導致重新render

組件的props 改變了,不一定觸發 render 函數的執行,但是如果 props 的值來自于父組件或者祖先組件的 state

在這種情況下,父組件或者祖先組件的 state 發生了改變,就會導致子組件的重新渲染

所以,一旦執行了setState就會執行render方法,useState 會判斷當前值有無發生改變確定是否執行render方法,一旦父組件發生渲染,子組件也會渲染

react Scheduler 原理

學習react也有一段時間了,最近零零碎碎看了些東西,總覺得改寫點東西沉淀下,聯系到react快速響應的理念,我覺得時間切片的使用是再出色不過了,時間切片的使用離不開scheduler,那就談談scheduler吧

scheduler是什么?

react16開始整個架構分成了三層,scheduler,Reconciler,renderer,因為為了實現將一個同步任務變成異步的可中斷的任務,react提出了fiber,因為最開始用的是stack,任務是無法中斷的,js執行時間太長時會影響頁面的渲染造成卡頓,fiber中任務是可以終端,但是中斷的任務怎么連上,什么時間執行,哪個先執行,這都屬于是新的問題,因此scheduler出生了,以瀏覽器是否有剩余時間作為任務中斷的標準,那么我們需要一種機制,當瀏覽器有剩余時間時,scheduler會通知我們,同時scheduler會進行一系列的任務優先級判斷,保證任務時間合理分配。

總結下scheduler的兩個功能:

  • 時間切片
  • 優先級調度

時間切片

JS腳本執行和瀏覽器布局、繪制不能同時執行。在每16.6ms時間內,需要完成 JS腳本執行 ----- 樣式布局 ----- 樣式繪制,當JS執行時間過長,超出了16.6ms,這次刷新就沒有時間執行樣式布局和樣式繪制了。

頁面掉幀,造成卡頓。時間切片是在瀏覽器每一幀的時間中,預留一些時間給JS線程,React利用這部分時間更新組件,預留的初始時間是5ms。

超過5ms,React將中斷js,等下一幀時間到來繼續執行js。其實瀏覽器本身已經實現了時間切片的功能,這個API叫requestIdleCallback,requestIdleCallback 是 window 屬性上的方法,它的作用是在瀏覽器一幀的剩余空閑時間內執行優先度相對較低的任務。

但是由于requestIdleCallback 的這兩個缺陷,react決定自己模擬時間切片

  • 1.瀏覽器兼容不好的問題
  • 2.requestIdleCallback 的 FPS 只有 20,也就是 50ms 刷新一次,遠遠低于頁面流暢度的要求

回顧一個知識瀏覽器在16.6ms里面要做哪些事情

宏任務-- 微任務 -- requestAnimationFrame -- 瀏覽器重排/重繪 -- requestIdleCallback

我們可以一起來看下時間切片應該放在哪里,首先排除requestIdleCallback,缺點上文已經提到了,實際上時間切片是放在宏任務里面的,可以先說下為什么不放在其他地方的原因:

1.為什么不是微任務里面

微任務將在頁面更新前全部執行完,所以達不到「將主線程還給瀏覽器」的目的。

2.為什么不使用requestAnimationFrame

如果第一次任務調度不是由 rAF() 觸發的,例如直接執行 scheduler.scheduleTask(),那么在本次頁面更新前會執行一次 rAF() 回調,該回調就是第二次任務調度。所以使用 rAF() 實現會導致在本次頁面更新前執行了兩次任務。

為什么是兩次,而不是三次、四次?因為在 rAF() 的回調中再次調用 rAF(),會將第二次 rAF() 的回調放到下一幀前執行,而不是在當前幀前執行。

另一個原因是 rAF() 的觸發間隔時間不確定,如果瀏覽器間隔了 10ms 才更新頁面,那么這 10ms 就浪費了。(現有 WEB 技術中并沒有規定瀏覽器應該什么何時更新頁面,所以通常認為是在一次宏任務完成之后,瀏覽器自行判斷當前是否應該更新頁面。如果需要更新頁面,則執行 rAF() 的回調并更新頁面。否則,就執行下一個宏任務。)

3.既然是宏任務,那么是settimeout嗎?

遞歸執行 setTimeout(fn, 0) 時,最后間隔時間會變成 4 毫秒,而不是最初的 1 毫秒,因為settimeout的執行時機是和js執行有關的,遞歸是會不準,最終使用 MessageChannel 產生宏任務,但是由于兼容,如果當前宿主環境不支持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,任務就會中斷,中斷之后再次執行就要用到調度了

任務調度

Scheduler對外暴露了一個方法unstable_runWithPriority,這個方法可以用來獲取優先級

unction unstable_runWithPriority(priorityLevel, eventHandler) {
? switch (priorityLevel) {
? ? case ImmediatePriority:
? ? case UserBlockingPriority:
? ? case NormalPriority:
? ? case LowPriority:
? ? case IdlePriority:
? ? ? break;
? ? default:
? ? ? priorityLevel = NormalPriority;
? }

//。。。省略
}

可以看到有5種優先級,比如,我們知道commit階段是同步執行的。可以看到,commit階段的起點commitRoot方法的優先級為ImmediateSchedulerPriority。

ImmediateSchedulerPriority即ImmediatePriority的別名,為最高優先級,會立即執行。可是優先級只是一個名稱,react如何判斷優先級的高低呢,這里我覺得和操作系統里面的一些概念還是挺相似的
給不同任務給上過期時間,誰快過期了就先執行誰

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,說明比當前時間還早,已經過期,必須快執行,初此之外,react新增了兩個隊列:已就緒任務 ,未就緒任務

所以,Scheduler存在兩個隊列:timerQueue:保存未就緒任務,taskQueue:保存已就緒任務

每當有新的未就緒的任務被注冊,我們將其插入timerQueue并根據開始時間重新排列timerQueue中任務的順序。當timerQueue中有任務就緒,即startTime <= currentTime,我們將其取出并加入taskQueue。

取出taskQueue中最早過期的任務并執行他。

簡單介紹下scheduler的原理,其實要更多了解scheduler,還要再看看lane模型,這塊之后再說吧,還有fiber啥的,有時間再寫。

總結

原文鏈接:https://blog.csdn.net/sadasddpk/article/details/122527924

欄目分類
最近更新