Reactjs 列表優化的一些心得
前言
在應用開發中,列表是我們使用頻率非常高的一種展現形式,在reactjs
專案中更是如此。無處不在的使用更是需要我們小心觸發效能瓶頸的深水炸彈。
下面就我最近的總結出的幾點心得分享給大家,有什麼問題歡迎批評指正。
不要用索引當key
值
reactjs
要求我們對列表中的每一項設定一個唯一的key
值,這個虛擬dom的diff演算法有很大關係。簡單的說,在同一級dom樹中,有2種情況會引起元素(這裡的元素指的是虛擬dom,而不是真正的dom元素)的增刪。
1.元素的型別改變
2.key
值變化
在列表中,元素型別一般是相同的,所以我們需要根據唯一的key
值來給當前元素加上標記,這樣reactjs
reactjs
採用的非常直接粗暴的演算法來判斷元素的增刪,比如
舊的
<li key={1}>a</li>
<li key={2}>b</li>
新的
<li key={1}>a</li> <li key={2}>b</li> <li key={3}>c</li> <li key={4}>d</li>
我們來分析下這種情況下的流程:
- 對比第一項
key
都是1,內容不變,不處理 - 對比第二項
key
都是2,內容不變,不處理 - 第三項
key
為3的是新的,新增 - 第四項
key
為4的是新的,新增
這個例子中我們使用索引(+1)作為key
,沒有什麼問題,完全符合我們的預期。接下我們看第二個
假設我們的的列表資料從
[
{id:2,text:'b'},
{id:3,text:'c'},
{id:5,text:'e'}
]
變成了
[ {id:1,text:'a'}, {id:2,text:'b'}, {id:3,text:'c'}, {id:4,text:'d'} ]
仍然使用索引作為key
,那麼渲染出來的應該是
舊的
<li key={1}>b</li>
<li key={2}>c</li>
<li key={3}>e</li>
新的
<li key={1}>a</li>
<li key={2}>b</li>
<li key={3}>c</li>
<li key={4}>d</li>
這種情況下的流程:
- 對比第一項
key
都是1,但是內容變了,替換文字 - 對比第二項
key
都是2,但是內容變了,替換文字 - 對比第三項
key
都是3,但是內容變了,替換文字 - 第四項
key
為4是新的,新增
這個和我們想的就有區別了,因為我們只是想做兩步操作,即替換第1個和新增第4個。而現在做了四步操作。
也許這個例子顯得沒有那麼糟糕,但是想象一下,如果是在一個50項的列表中插入1個新的到頭部,那麼後面的50項將都會進行dom
更新渲染,如果每一項內容是複雜的元件,那麼代價更加高昂,而我們其實只是想在第一個元素前插入一條。
那麼如果解決上面的問題呢,其實很簡單,我們的列表資料都有唯一的id
值,而實際開發中我們列表中一般都會存在這樣的唯一值,我們只需要把它複製給key
即可。
那麼我們的列表會變成這樣
舊的
<li key={2}>b</li>
<li key={3}>c</li>
<li key={5}>e</li>
新的
<li key={1}>a</li>
<li key={2}>b</li>
<li key={3}>c</li>
<li key={4}>d</li>
這種情況下的流程:
- 第一項
key
為1是新的,新增,節點變成4個 - 對比第二項
key
都是2,內容不變,不處理 - 對比第三項
key
都是3,內容不變,不處理 - 第四項
key
為4,舊的是5,替換節點
將列表和列表項單獨寫成純元件
為什麼?
我們可能已經習慣這樣寫列表
render(){
render (
<ul>
{this.state.list.map(item=><li key={item.id}>{item.text}<li>)}
</ul>
)
}
在大多數情況下,這樣寫沒有什麼問題,reactjs
執行速度很快,但是有些情況下,這樣寫可能成為導致網頁卡頓的罪魁禍首。
每當我們改變元件狀態的時候,reactjs
都會重建當前元件的整個虛擬dom樹,也就是說不管你的state.list
是否有改變,整個樹都會重建,而這個時候列表的渲染是不必要的,當列表過長,元件狀態更新頻繁,甚至手機效能不佳的情況下,不斷的重新建立虛擬dom樹很有可能會導致頁面幀數下降。
PureComponent
PureComponent和Component
沒什麼什麼區別,除了它預設在shouldUpdateComponent
裡面預設做了淺比較,如果相同,則不會觸發更新渲染。
在reactjs
中,資料推薦處理成不可變資料(這裡不是指immutable.js
,而是說物件始終是不變的,如果資料有變了,必須是新的物件),配合redux
的時候更是如此。所以如果list
發生改變時,傳入的必然是新的物件,這個時候會觸發列表元件更新。
使用
class List extends PureComponent{
render(){
return (
<ul>
{this.props.list.map(item=><li key={item.id}>{item.text}<li>)}
</ul>
)
}
}
/*** parent ***/
// .....
render(){
render (
<List list = {this.state.list}/>
)
}
// ...
子元件
當我們列表的子元素是複雜的組建時,我們也應該單獨提取成PureComponent
,以避免不必要的渲染,事實上,我覺得大多陣列件都可以使用PureComponent
替換Component
。
不要在屬性上箭頭函式
箭頭函式很方便,不僅寫法簡單還能保持this
指向父級作用域。
為了維護事件處理函式的this
,我們經常在元件中看到它類似這樣的使用
<Component onClick={()=>{alert(11)} />
但是這樣寫會有幾個問題
- 每次
render
都會重新建立一個新的函式,瀏覽器建立和回收物件都會有開銷,如果是列表,那麼每個列表項都會建立和銷燬。 - 因為每次
render
都是傳入新的函式,shouldUpdateComponent
淺比較必然不相等,會導致PureComponent
元件失去應有效果。
正確的做法
如果使用了transform-class-properties
handleClick = ()=>{
alert(1)
}
<Component onClick={this.handleClick} />
或者
constructor(){
super(...arguments)
this.handleClick = this.handleClick.bind(this)
}
handleClick = ()=>{
alert(1)
}
結束語
暫時就總結了這些吧,以後有新的心得再更新,歡迎交流留言。