react render的原理及觸發(fā)時(shí)機(jī)說(shuō)明
react render的原理及觸發(fā)時(shí)機(jī)
理解react的render函數(shù),要從這三點(diǎn)來(lái)認(rèn)識(shí)。原理、執(zhí)行時(shí)機(jī)、總結(jié)。
原理
在類(lèi)組件和函數(shù)組件中,render函數(shù)的形式是不同的。
在類(lèi)組件中render函數(shù)指的就是render
方法;而在函數(shù)組件中,指的就是整個(gè)函數(shù)組件。
class?Foo?extends?React.Component?{ ????render()?{ //類(lèi)組件中 ????????return?<h1>?Foo?</h1>; ????} }
function?Foo()?{? //函數(shù)組件中 ????return?<h1>?Foo?</h1>; }
在render函數(shù)中的jsx語(yǔ)句會(huì)被編譯成我們熟悉的js代碼
在render
過(guò)程中,React
將新調(diào)用的 render
函數(shù)返回的樹(shù)與舊版本的樹(shù)進(jìn)行比較,這一步是決定如何更新 DOM
的必要步驟,然后進(jìn)行 diff
比較,更新 DOM
樹(shù)
觸發(fā)時(shí)機(jī)
render
的執(zhí)行時(shí)機(jī)主要分成了兩部分:
類(lèi)組件調(diào)用 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> ? ? ); ? } }
函數(shù)組件通過(guò)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> ? );? }
函數(shù)組件通過(guò)useState
這種形式更新數(shù)據(jù),當(dāng)數(shù)組的值不發(fā)生改變了,就不會(huì)觸發(fā)render
小結(jié)一下:
render函數(shù)里面可以編寫(xiě)JSX,轉(zhuǎn)化成createElement這種形式,用于生成虛擬DOM,最終轉(zhuǎn)化成真實(shí)DOM
在React 中,類(lèi)組件只要執(zhí)行了 setState 方法,就一定會(huì)觸發(fā) render 函數(shù)執(zhí)行,函數(shù)組件使用useState更改狀態(tài)不一定導(dǎo)致重新render
組件的props 改變了,不一定觸發(fā) render 函數(shù)的執(zhí)行,但是如果 props 的值來(lái)自于父組件或者祖先組件的 state
在這種情況下,父組件或者祖先組件的 state 發(fā)生了改變,就會(huì)導(dǎo)致子組件的重新渲染
所以,一旦執(zhí)行了setState就會(huì)執(zhí)行render方法,useState 會(huì)判斷當(dāng)前值有無(wú)發(fā)生改變確定是否執(zhí)行render方法,一旦父組件發(fā)生渲染,子組件也會(huì)渲染
react Scheduler 原理
學(xué)習(xí)react也有一段時(shí)間了,最近零零碎碎看了些東西,總覺(jué)得改寫(xiě)點(diǎn)東西沉淀下,聯(lián)系到react快速響應(yīng)的理念,我覺(jué)得時(shí)間切片的使用是再出色不過(guò)了,時(shí)間切片的使用離不開(kāi)scheduler,那就談?wù)剆cheduler吧
scheduler是什么?
react16開(kāi)始整個(gè)架構(gòu)分成了三層,scheduler,Reconciler,renderer,因?yàn)闉榱藢?shí)現(xiàn)將一個(gè)同步任務(wù)變成異步的可中斷的任務(wù),react提出了fiber,因?yàn)樽铋_(kāi)始用的是stack,任務(wù)是無(wú)法中斷的,js執(zhí)行時(shí)間太長(zhǎng)時(shí)會(huì)影響頁(yè)面的渲染造成卡頓,fiber中任務(wù)是可以終端,但是中斷的任務(wù)怎么連上,什么時(shí)間執(zhí)行,哪個(gè)先執(zhí)行,這都屬于是新的問(wèn)題,因此scheduler出生了,以瀏覽器是否有剩余時(shí)間作為任務(wù)中斷的標(biāo)準(zhǔn),那么我們需要一種機(jī)制,當(dāng)瀏覽器有剩余時(shí)間時(shí),scheduler會(huì)通知我們,同時(shí)scheduler會(huì)進(jìn)行一系列的任務(wù)優(yōu)先級(jí)判斷,保證任務(wù)時(shí)間合理分配。
總結(jié)下scheduler的兩個(gè)功能:
- 時(shí)間切片
- 優(yōu)先級(jí)調(diào)度
時(shí)間切片
JS腳本執(zhí)行和瀏覽器布局、繪制不能同時(shí)執(zhí)行。在每16.6ms時(shí)間內(nèi),需要完成 JS腳本執(zhí)行 ----- 樣式布局 ----- 樣式繪制,當(dāng)JS執(zhí)行時(shí)間過(guò)長(zhǎng),超出了16.6ms,這次刷新就沒(méi)有時(shí)間執(zhí)行樣式布局和樣式繪制了。
頁(yè)面掉幀,造成卡頓。時(shí)間切片是在瀏覽器每一幀的時(shí)間中,預(yù)留一些時(shí)間給JS線(xiàn)程,React利用這部分時(shí)間更新組件,預(yù)留的初始時(shí)間是5ms。
超過(guò)5ms,React將中斷js,等下一幀時(shí)間到來(lái)繼續(xù)執(zhí)行js。其實(shí)瀏覽器本身已經(jīng)實(shí)現(xiàn)了時(shí)間切片的功能,這個(gè)API叫requestIdleCallback,requestIdleCallback 是 window 屬性上的方法,它的作用是在瀏覽器一幀的剩余空閑時(shí)間內(nèi)執(zhí)行優(yōu)先度相對(duì)較低的任務(wù)。
但是由于requestIdleCallback 的這兩個(gè)缺陷,react決定自己模擬時(shí)間切片
- 1.瀏覽器兼容不好的問(wèn)題
- 2.requestIdleCallback 的 FPS 只有 20,也就是 50ms 刷新一次,遠(yuǎn)遠(yuǎn)低于頁(yè)面流暢度的要求
回顧一個(gè)知識(shí)瀏覽器在16.6ms里面要做哪些事情
宏任務(wù)-- 微任務(wù) -- requestAnimationFrame -- 瀏覽器重排/重繪 -- requestIdleCallback
我們可以一起來(lái)看下時(shí)間切片應(yīng)該放在哪里,首先排除requestIdleCallback,缺點(diǎn)上文已經(jīng)提到了,實(shí)際上時(shí)間切片是放在宏任務(wù)里面的,可以先說(shuō)下為什么不放在其他地方的原因:
1.為什么不是微任務(wù)里面
微任務(wù)將在頁(yè)面更新前全部執(zhí)行完,所以達(dá)不到「將主線(xiàn)程還給瀏覽器」的目的。
2.為什么不使用requestAnimationFrame
如果第一次任務(wù)調(diào)度不是由 rAF() 觸發(fā)的,例如直接執(zhí)行 scheduler.scheduleTask(),那么在本次頁(yè)面更新前會(huì)執(zhí)行一次 rAF() 回調(diào),該回調(diào)就是第二次任務(wù)調(diào)度。所以使用 rAF() 實(shí)現(xiàn)會(huì)導(dǎo)致在本次頁(yè)面更新前執(zhí)行了兩次任務(wù)。
為什么是兩次,而不是三次、四次?因?yàn)樵?rAF() 的回調(diào)中再次調(diào)用 rAF(),會(huì)將第二次 rAF() 的回調(diào)放到下一幀前執(zhí)行,而不是在當(dāng)前幀前執(zhí)行。
另一個(gè)原因是 rAF() 的觸發(fā)間隔時(shí)間不確定,如果瀏覽器間隔了 10ms 才更新頁(yè)面,那么這 10ms 就浪費(fèi)了。(現(xiàn)有 WEB 技術(shù)中并沒(méi)有規(guī)定瀏覽器應(yīng)該什么何時(shí)更新頁(yè)面,所以通常認(rèn)為是在一次宏任務(wù)完成之后,瀏覽器自行判斷當(dāng)前是否應(yīng)該更新頁(yè)面。如果需要更新頁(yè)面,則執(zhí)行 rAF() 的回調(diào)并更新頁(yè)面。否則,就執(zhí)行下一個(gè)宏任務(wù)。)
3.既然是宏任務(wù),那么是settimeout嗎?
遞歸執(zhí)行 setTimeout(fn, 0) 時(shí),最后間隔時(shí)間會(huì)變成 4 毫秒,而不是最初的 1 毫秒,因?yàn)閟ettimeout的執(zhí)行時(shí)機(jī)是和js執(zhí)行有關(guān)的,遞歸是會(huì)不準(zhǔn),最終使用 MessageChannel 產(chǎn)生宏任務(wù),但是由于兼容,如果當(dāng)前宿主環(huán)境不支持MessageChannel,則使用setTimeout。
在React的render階段,開(kāi)啟Concurrent Mode時(shí),每次遍歷前,都會(huì)通過(guò)Scheduler提供的shouldYield方法判斷是否需要中斷遍歷,使瀏覽器有時(shí)間渲染:
function workLoopConcurrent() { ? // Perform work until Scheduler asks us to yield ? while (workInProgress !== null && !shouldYield()) { ? ? performUnitOfWork(workInProgress); ? } }
是否中斷的依據(jù),最重要的一點(diǎn)便是每個(gè)任務(wù)的剩余時(shí)間是否用完。
在Schdeduler中,為任務(wù)分配的初始剩余時(shí)間為5ms。如果shouldYield為true,任務(wù)就會(huì)中斷,中斷之后再次執(zhí)行就要用到調(diào)度了
任務(wù)調(diào)度
Scheduler對(duì)外暴露了一個(gè)方法unstable_runWithPriority,這個(gè)方法可以用來(lái)獲取優(yōu)先級(jí)
unction unstable_runWithPriority(priorityLevel, eventHandler) { ? switch (priorityLevel) { ? ? case ImmediatePriority: ? ? case UserBlockingPriority: ? ? case NormalPriority: ? ? case LowPriority: ? ? case IdlePriority: ? ? ? break; ? ? default: ? ? ? priorityLevel = NormalPriority; ? } //。。。省略 }
可以看到有5種優(yōu)先級(jí),比如,我們知道commit階段是同步執(zhí)行的??梢钥吹?,commit階段的起點(diǎn)commitRoot方法的優(yōu)先級(jí)為ImmediateSchedulerPriority。
ImmediateSchedulerPriority即ImmediatePriority的別名,為最高優(yōu)先級(jí),會(huì)立即執(zhí)行。可是優(yōu)先級(jí)只是一個(gè)名稱(chēng),react如何判斷優(yōu)先級(jí)的高低呢,這里我覺(jué)得和操作系統(tǒng)里面的一些概念還是挺相似的
給不同任務(wù)給上過(guò)期時(shí)間,誰(shuí)快過(guò)期了就先執(zhí)行誰(shuí)
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,說(shuō)明比當(dāng)前時(shí)間還早,已經(jīng)過(guò)期,必須快執(zhí)行,初此之外,react新增了兩個(gè)隊(duì)列:已就緒任務(wù) ,未就緒任務(wù)
所以,Scheduler存在兩個(gè)隊(duì)列:timerQueue:保存未就緒任務(wù),taskQueue:保存已就緒任務(wù)
每當(dāng)有新的未就緒的任務(wù)被注冊(cè),我們將其插入timerQueue并根據(jù)開(kāi)始時(shí)間重新排列timerQueue中任務(wù)的順序。當(dāng)timerQueue中有任務(wù)就緒,即startTime <= currentTime,我們將其取出并加入taskQueue。
取出taskQueue中最早過(guò)期的任務(wù)并執(zhí)行他。
簡(jiǎn)單介紹下scheduler的原理,其實(shí)要更多了解scheduler,還要再看看lane模型,這塊之后再說(shuō)吧,還有fiber啥的,有時(shí)間再寫(xiě)。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
react嵌套路由實(shí)現(xiàn)TabBar的實(shí)現(xiàn)
本文主要介紹了react嵌套路由實(shí)現(xiàn)TabBar的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08react PropTypes校驗(yàn)傳遞的值操作示例
這篇文章主要介紹了react PropTypes校驗(yàn)傳遞的值操作,結(jié)合實(shí)例形式分析了react PropTypes針對(duì)傳遞的值進(jìn)行校驗(yàn)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2020-04-04React Native 自定義下拉刷新上拉加載的列表的示例
本篇文章主要介紹了React Native 自定義下拉刷新上拉加載的列表的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03為什么說(shuō)form元素是React的未來(lái)
這篇文章主要介紹了為什么說(shuō)form元素是React的未來(lái),本文會(huì)帶你聊聊React圍繞form的布局與發(fā)展,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06React中setState/useState的使用方法詳細(xì)介紹
這篇文章主要介紹了React中setState/useState的使用方法,useState 和 setState 在React開(kāi)發(fā)過(guò)程中 使用很頻繁,但很多人都停留在簡(jiǎn)單的使用階段,并沒(méi)有正在了解它們的執(zhí)行機(jī)制2023-04-04React css-in-js基礎(chǔ)介紹與應(yīng)用
隨著React、Vue等支持組件化的MVVM前端框架越來(lái)越流行,在js中直接編寫(xiě)css的技術(shù)方案也越來(lái)越被大家所接受。為什么前端開(kāi)發(fā)者們更青睞于這些css-in-js的方案呢,下面帶你了解它2022-09-09簡(jiǎn)單的React SSR服務(wù)器渲染實(shí)現(xiàn)
這篇文章主要介紹了簡(jiǎn)單的React SSR服務(wù)器渲染實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12