寫了一段時間的
react
以後,漸漸的喜歡上了使用react
來寫應用。javascript咱們知道,
react
時打出的旗號之一就是高性能。php今天咱們還一塊兒來聊一聊
react
的性能優化,思考還能經過哪些手段來提高React的性能,使咱們的react
更快,性能更好。java
再講性能優化以前,咱們須要先來了解一下如何查看react加載組件時所耗費的時間的工具,在react 16版本以前咱們可使用React Perf
來查看。react
你們能夠在chorme中先安裝React Perf擴展,而後在入口文件或者redux
的store.js
中加入相應的代碼便可:git
在最新的React16版本中,咱們能夠直接在url後加上?react_pref
,就能夠在chrome瀏覽器的performance
,咱們能夠查看User Timeing
來查看組件的加載時間。github
使用此工具的具體操做你們能夠看下圖:算法
首先咱們先思考一個問題,好比我要實現一個點擊按鈕使相應的num
增長1,咱們有哪一些方法。chrome
你們應該都能想到,無非就是三種,以下圖:編程
第一種是在構造函數中綁定this
,第二種是在render()
函數裏面綁定this
,第三種就是使用箭頭函數,都能實現上述方法;redux
可是哪種方法的性能最好,是咱們要考慮的問題。應該你們都知道答案:第一種的性能最好。
由於第一種,構造函數每一次渲染的時候只會執行一遍;
而第二種方法,在每次render()
的時候都會從新執行一遍函數;
第三種方法的話,每一次render()
的時候,都會生成一個新的箭頭函數,即便兩個箭頭函數的內容是同樣的。
第三種方法咱們能夠舉一個例子,由於react
判斷是否須要進行render
是淺層比較,簡單來講就是經過===
來判斷的,若是state
或者prop
的類型是字符串或者數字,只要值相同,那麼淺層比較就會認爲其相同;
可是若是前者的類型是複雜的對象的時候,咱們知道對象是引用類型,淺層比較只會認爲這兩個prop
是否是同一個引用,若是不是,哪怕這兩個對象中的內容徹底同樣,也會被認爲是兩個不一樣的prop
。
舉個例子:
當咱們給組件Foo
給名爲style
的prop
賦值;
<Foo style={{ color:"red" }}
複製代碼
使用這種方法,每一次渲染都會被認爲是一個style
這個prop
發生了變化,由於每一次都會產生一個對象給style
。
那麼咱們應該如何改進,若是想要讓react
渲染的時候認爲先後對象類型prop
相同,則必需要保證prop
指向同一個javascript
對象,以下:
const fooStyle = { color: "red" }; //取保這個初始化只執行一次,不要放在render中,能夠放在構造函數中
<Foo style={fooStyle} />
複製代碼
這個問題是咱們在平時的編碼中能夠避免的。
shouldComponentUpdate
是決定react
組件何時可以不從新渲染的函數,可是這個函數默認的實現方式就是簡單的返回一個true
。也就是說,默認每次更新的時候都要調用所用的生命週期函數,包括render
函數,從新渲染。
咱們來看一下下面的一個例子
咱們寫兩個組件,App
和Demo
組件,並寫兩個方法,一個改變App
中的num
的值,一個是改變title
,咱們在Demo的render中打印render函數。咱們能夠看到如下的效果:
咱們能夠清晰的看到雖然demo
組件裏的title
值沒有改變,可是仍是render
了。
爲了解決這個問題,咱們能夠對demo組件進行以下的修改:
只有當demo的title值發生改變的時候,咱們纔去render,咱們能夠看一下效果:
以上只是一個特別簡單的一個對於shouldComponentUpdate
的定製。
在最新的react
中,react給咱們提供了React.PureComponent
,官方也在早期提供了名爲react-addons-pure-render-mixin
插件來從新實現shouldComponentUpdate
生命週期方法。
經過上述的方法的效果也是和咱們定製shouldComponentUpdate
的效果是一致的。
可是咱們要注意的是,這裏的PureRender
是淺比較的,由於深比較的場景是至關昂貴的。因此咱們要注意咱們在1.1
中說到的一些注意點:不要直接爲props設置對象或者數組、不要將方法直接綁定在元素上,由於其實函數也是對象
先配上一張經典的圖和經典的一句話:
Shared mutable state is the root of all evil(共享的可變狀態是萬惡之源)
-- Pete Hunt
javascript
中的對象通常都是可變的,由於使用了引用賦值,新的對象簡單的引用了原始對象,改變新對象將影響到原始對象。
舉個例子:
foo = { a : 1 };
bar = foo;
bar.a = 2;
複製代碼
當咱們給bar.a
賦值後,會發現foo.a
也變成了2,雖然咱們能夠經過深拷貝與淺拷貝解決這個問題,可是這樣作很是的昂貴,對cpu
和內存會形成浪費。
這裏就須要用到Immutable
,經過Immutable
建立的Immutable Data
一旦被建立,就不能再更改。對Immutable
對象進行修改、添加或刪除操做,都會返回一個新的Immutable
對象。
這裏咱們將一下其中三個比較重要的數據結構
Object
,Es6
種也有專門的Map
對象Array
咱們能夠看兩個例子:
使用Map
生成一個immutable
對象
import { Map , is } from 'immutable';
let obj = Map({
'name': 'react study',
'course': Map({name: 'react+redux'})
})
let obj1 = obj.set('name','darrell');
console.log(obj.get('course') === obj1.get('course')); // 返回true
console.log(obj === obj1); // 返回false
複製代碼
Immutable.is
比較的是兩個對象的 hashCode
或 valueOf
(對於 JavaScript 對象)。因爲 immutable 內部使用了 Trie 數據結構來存儲,只要兩個對象的 hashCode
相等,值就是同樣的。這樣的算法避免了深度遍歷比較,性能很是好。
let obj = Map({name:1,title:'react'});
let obj1 = Map({name:1,title:'react'});
console.log(is(obj,obj1)); // 返回true
let obj2 = {name:1,title:'react'};
let obj3 = {name:1,title:'react'};
console.log(is(obj2,obj3)); // 返回false
複製代碼
Immutable
優勢:
Immutable
缺點:
若是你們想深刻了解,能夠參考immutable、IMMUTABLE 詳解。
react
組件在裝載過程當中,react
經過在render
方法在內存中產生一個樹形結構,樹上的節點表明一個react
組件或者原生的Dom
元素,這個樹形結構就是咱們所謂的Vitural Dom
,react根據這個來渲染產生瀏覽器的Dom
樹。
react
在更新階段對比原有的Vitural Dom
和新生成的Vitural Dom
,找出不一樣之處,在根據不一樣來渲染Dom樹。
react爲了追求高性能,採用了時間複雜度爲O(N)
來比較兩個屬性結構的區別,由於要確切比較兩個樹形結構,須要經過O(N^3)
,這會下降性能。
咱們舉幾個狀況,你們就會立刻理解:
節點類型不一樣
// A組件
<div>
<Todos /> </div>
// B組件
<span>
<Todos /> </span>
複製代碼
咱們想把A
組件更新成B
組件,react
在作比較的時候,發現最外面的根結點不同,直接就廢掉了以前的<div>
節點,包括裏面的子節點,這是一個巨大的浪費,可是爲了不O(N^3)
的時間複雜度,只能採用這種方式
因此在開發過程當中,咱們應該儘可能避免上面的狀況,不要將包裹節點的類型隨意改變。
兩個節點類型同樣
這裏包括兩種狀況,一種是節點是Dom
類型,還有一種react
組件。
對於dom
類型,咱們舉個例子:
// A組件
<div style={{color: 'red',fontSize:15}} className="welcome">
Hello World!!!
</div>
// B組件
<div style={{color: 'green',fontSize:15}} className="react">
Good Bye!!!
</div>
複製代碼
上述A和B組件的區別是文字、className
、style
中的color
發生改變,由於Dom
元素沒變,React
只會修改他變化的部分。
針對react
組件類型,渲染無非就是在走一遍組件實例的更新過程,最主要的就是定製shouldComponentUpdate
,咱們上面也有講到,就不細講了。
多個子組件狀況
咱們看兩個例子就能明白
例子一:
// A
<ul>
<TodoItem text="First" complete={false} />
<TodoItem text="Second" complete={false} />
</ul>
// B
<ul>
<TodoItem text="First" complete={false} />
<TodoItem text="Second" complete={false} />
<TodoItem text="Third" complete={false} />
</ul>
複製代碼
從A變到B,若是shouldComponentUpdate
處理得當,咱們只須要更新裝載third
的那一次就行。
咱們來看看下一個例子:
// A
<ul>
<TodoItem text="First" complete={false} />
<TodoItem text="Second" complete={false} />
</ul>
// B
<ul>
<TodoItem text="Zero" complete={false} />
<TodoItem text="First" complete={false} />
<TodoItem text="Second" complete={false} />
</ul>
複製代碼
這裏由於react是採用O(n)的時間複雜度,因此會依次將text爲First的改成Zero,text爲Second改成First,在最後再加上一個組件,text爲Second。現存的兩個的text的屬性都被改變了,因此會依次渲染。
若是咱們這裏有1000個實例,那麼就會發生1000次更新。
這裏咱們就要用到Key
了
簡單來講,其實這一個Key就是react組件的身份證號。
咱們將上一個例子改爲以下,就能夠避免上面的問題了,react就可以知道其實B裏面的第二個和第三個組件其實就是A中的第一個和第二個實例。
// A
<ul>
<TodoItem key={1} text="First" complete={false} />
<TodoItem key={2} text="Second" complete={false} />
</ul>
// B
<ul>
<TodoItem key={0} text="Zero" complete={false} />
<TodoItem key={1} text="First" complete={false} />
<TodoItem key={2} text="Second" complete={false} />
</ul>
複製代碼
不過如今,react也會提醒咱們不要忘記使用key
,若是沒有加,在瀏覽器中會報錯。
關於key
的使用咱們要注意的是,這個key值要穩定不變的,就如同身份證號之於咱們是穩定不變的同樣。
一個常見的錯誤就是,拿數組的的下標值去當作key,這個是很危險的,代碼以下,咱們必定要避免。
<ul>
{
todos.map((item, index) => {
<TodoItem
key={index}
text={item.text}
completed={item.completed}
})
}
</ul>
複製代碼
在前面的優化過程當中,咱們都是優化渲染來提升性能的,既然react
和redux
都是經過數據驅動的的方式驅動渲染過程,那麼處理優化渲染過程,獲取數據的過程也是須要考慮的一個優化點。
//下面是redux中簡單的一個篩選功能
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
複製代碼
mapStateToProps
函數做爲redux store
中獲取數據的重要一環,當咱們根據filter
和todos
來顯示相應的待辦事項的時候,咱們都要遍歷todos
字段上的數組。
當數組比較大的時候,則會下降性能。
這個時候,reselect就應運而生了,它的動做原理:只要相關的狀態沒有改變,那麼就直接使用上一次的緩存結果。
具體的用法我就不在這裏過多介紹了,已經有不少的牛人寫了相關的文章,我也不重複寫了,你們若是想深刻了解的話,能夠參考reselect的giuhub、Redux的中間件-Reselect。
此篇文章是參考了《深刻React技術棧》和《深刻淺出React與Redux》這兩本書中關於對react性能優化的章節,再加上本身的動手實踐與思考寫的。
文章中不乏會有些錯誤的地方還請你們多多批評指正。