以下,是我在很多人都使用過react,但是很少人能說出它內部的渲染原理。有人會說,會用就行了,知道渲染原理有必要麼?其實渲染原理決定著效能優化的方法,只有在了解原理之後,才能完全理解為什麼這樣做可以優化效能。正所謂:知其然,然後知其所以然。2018 react conf 的分享內容,希望對大家有所幫助。可以先在
ppt對照看,效果更佳哦~☺。
廢話不多說,下面我們就開始吧~
當我們寫下一段jsx**的時候,react是如何根據我們的jsx**來生成虛擬dom的組成元素element的。
再生成elment之後,react又如何將其轉成瀏覽器的真實節點。這裡會通過介紹首次渲染以及更新渲染的流程來幫助大家理解這個渲染流程,
結合渲染原理,通過實際例子,看看如何優化元件。
到目前為止,這些優化元件的方法還不能解決什麼問題,所以我們需要引入非同步渲染,以及非同步渲染的原理是什麼。
上篇文章介紹了首次渲染的過程,接下來我們來看更新渲染的過程。
觸發元件的更新有兩種更新方式:props以及state改變帶來的更新。本次主要解析state改變帶來的更新。整個過程流程圖如下:
1、一般改變state,都是從setstate開始,這個函式被呼叫之後,會將我們傳入的state放進pendingstate的陣列裡存起來,然後判斷當前流程是否處於批量更新,如果是,則將當前元件的instance放進dirtycomponent裡,當這個更新流程中所有需要更新的元件收集完畢之後(這裡面涉及到事務的概念,感興趣的可以自己去了解一下)就會遍歷dirtycomponent這個陣列,呼叫他們的uptatecomponent對元件進行更新。當然,如果當前不處於批量更新的狀態,會直接去遍歷dirtycomponent進行更新。
最後這個需要去更新節點的時候,跟首次渲染一樣,也需要呼叫reactdomcomponent的updatecomponent來更新。其中第二步render得到的也是自定義元件的話, 會形成遞迴呼叫。
接下來,還是上次的問題:那麼更新過程中的生命週期函式,shouldcomponentupdate,componentwillupdate跟componentdidupdate在哪被呼叫呢?
由圖可知,shouldcomponentupdate在第一步呼叫得到nextstate之後呼叫,因為nextstate也是它的其中乙個引數嘛~這個函式很重要,它是我們效能優化的乙個很關鍵的點:由圖可以看到,當shouldcomponentupdate返回false的時候,下面的一大塊都不會被去執行,包括已經被優化的diff演算法。
當shouldcomponentupdate返回true的時候,會先呼叫componentwillupdate,在整個更新過程結束之後呼叫componentdidupdate。
以上就是更新渲染的過程。
下面我們重點再來介紹這個過程中的diff演算法。
diff演算法
react基於兩個假設:
兩個相同的元件產生類似的dom結構,不同元件產生不同dom結構對於同一層次的一組子節點,它們可以通過唯一的id區分
發明了一種叫diff的演算法來比較兩棵dom tree,它極大的優化了這個比較的過程,將演算法複雜度從o(n^3)降低到o(n)。
同時,基於第一點假設,我們可以推論出,diff演算法只會對同層的節點進行比較。如圖,它只會對顏色相同的節點進行比較。
也就是說如果父節點不同,react將不會在去對比子節點。因為不同的元件dom結構會不相同,所以就沒有必要在去對比子節點了。這也提高了對比的效率。
下面,我們具體看下diff演算法是怎麼做的,這裡分為三種情況考慮
不同節點型別
對於不同的節點型別,react會基於第一條假設,直接刪去舊的節點,新建乙個新的節點。
比如:
// 由shape1到shape2
react會直接刪掉a節點(包括它所有的子節點),然後新建乙個b節點插入
- codesandboxlyop4w9x9m - codesandbox
最後終端輸出的結果是:
shape1 :
a is created
a render
c is created
c render
c componentdidmount
a componentdidmount
shape2 :
a componentwillunmount
c componentwillunmount
b is created
b render
c is created
c render
c componentdidmount
b componentdidmount
由此可以看出,a與其子節點c會被直接刪除,然後重新建乙個b,c插入。這樣就給我們的效能優化提供了乙個思路,就是我們要保持dom標籤的穩定性。
打個比方,如果寫了乙個
(list 是乙個有幾千個節點的元件),切換的時候變成了,此時即使list的內容不變,它也會先被解除安裝在建立,其實是很浪費的。
相同節點型別
當對比相同的節點型別比較簡單,這裡分為兩種情況,一種是dom元素型別,對應html直接支援的元素型別:div,span和p,還有一種是自定義元件。
react會對比它們的屬性,只改變需要改變的屬性
比如:
這兩個div中,react會只更新classname的值
這兩個div中,react只會去更新color的值
由於react此時並不知道如何去更新dom樹,因為這些邏輯都在react元件裡面,所以它能做的就是根據新節點的props去更新原來根節點的元件例項,觸發乙個更新的過程,最後在對所有的child節點在進行diff的遞迴比較更新。
- shouldcomponentupdate
- componentwillreceiveprops
- componentwillupdate
- render
- componentdidupdate
子節點比較
// 列表一到列表二
因為react在沒有key的情況下對比節點的時候,是乙個乙個按著順序對比的。從列表一到列表二,只是在中間插入了乙個c,但是如果沒有key的時候,react會把b刪去,新建乙個c放在b的位置,然後重新建乙個節點b放在尾部。
你說什麼就是什麼咯?!不信的話,我們還是跑一邊**,看看生命週期驗證一下,連線位址為:lpl52wy9vl - codesandbox
列表一:
a is created
a render
b is created
b render
a componentdidmount
b componentdidmount
列表二:
a render
b componentwillunmount
c is created
c render
b is created
b render
a componentdidupdate
c componentdidmount
b componentdidmount
當節點很多的時候,這樣做是非常低效的。有兩種方法可以解決這個問題:
1、保持dom結構的穩定性,我們來看這個變化,由兩個子節點變成了三個,其實是乙個不穩定的dom結構,我們可以通過通過加乙個null,保持dom結構的穩定。這樣按照順序對比的時候,b就不會被解除安裝又重建回來。
// 列表一到列表二
2、key
通過給節點配置key,讓react可以識別節點是否存在。
配上key之後,在跑一遍**看看。
a render
c is created
c render
b render
a componentdidupdate
c componentdidmount
b componentdidupdate
果然,配上key之後,列表二的生命週期就如我所願,只在指定的位置建立c節點插入。
這裡要注意的一點是,key值必須是穩定(所以我們不能用math.random()去建立key),可**,並且唯一的。
這裡給我們效能優化也提供了兩個非常重要的依據:
原文
react更新渲染及渲染原理
觸發元件的更新有兩種更新方式 props以及state改變帶來的更新。本次主要解析state改變帶來的更新。整個過程流程圖如下 1 一般改變state,都是從setstate開始,這個函式被呼叫之後,會將我們傳入的state放進pendingstate的陣列裡存起來,然後判斷當前流程是否處於批量更新...
Android UI效能優化(渲染)
谷歌05年初發布效能優化典範,是的開發者學會如何去識別 診斷 解決安卓應用開發中所存在的效能問題。首當其衝的便是渲染的效能優化。在正常的開發中,可以移除不必要的background,以減少繪製的程度。如設定了主布局檔案的背景顏色,就可以移除listview以及listview的items中的一些ba...
web效能優化 瀏覽器渲染原理
在web效能優化 瀏覽器工作原理中講到,瀏覽器渲染是在renderer process中完成的。那我們來看下renderer process究竟幹了什麼?renderer process包含的執行緒有 1.主線程 main thread 2.工作執行緒 workder thread 3.合成器執行緒...