React性能探索 — 避免不必要渲染
- 2019 年 12 月 4 日
- 筆記
本文作者:IMWeb 黃qiong 原文出處:IMWeb社區 未經同意,禁止轉載
背景
上一篇文章的結尾 http://imweb.io/topic/5985cc4d35d7d0a321c5eb75 我們說到,也許,不是所有的節點都需要重新渲染,對於那些不需要渲染的節點,我們如何找到它們並做優化呢?
本篇文章來具體解答這個問題。
應用分析
首先,先看這個應用:頁面的兩部分分別渲染5000個節點,從1-5000。當點擊按鈕之後,第二部分的節點會更新,重新渲染從2-5001的數字,但是第一部分保持不變。
import React, { createElement, Component } from 'react'; import {render} from 'react-dom'; import Perf from 'react-addons-perf'; import ListItem from './ListItem' function arrayGenerator(length) { return Array.apply(null, { length: length }).map(Number.call, Number) } class App extends Component { constructor(props) { super(props) this.state = { multiplier: 1 } } resetMultiplier() { this.setState({ multiplier: 2 }) } render() { return ( <div className="App"> <button onClick={this.resetMultiplier.bind(this)}>Click Me</button> <ul> { arrayGenerator(5000).map(i => { return <ListItem key={i} text={i}/> }) } { arrayGenerator(5000).map(i => { return <ListItem key={i} text={i + this.state.multiplier}/> }) } </ul> </div> ); } } render(<App />,document.getElementById('main'));
gitbug 鏈接: https://github.com/hhhuangqiong/performance-for-react
感興趣的同學可以下載跑一跑程式碼
分析更新時間
這裡用react的Perf工具來測量重新渲染的時間。
使用方法:
npm install --save-dev react-addons-perf import Perf from 'react-addons-perf'
這裡主要用到四個方法:
- Perf.start():開始計時
- Perf.stop():結束計時
- Perf.printInclusive():列印組件總的渲染時間
- Perf.printWasted():列印浪費的時間
開始計時的函數,我把它放到resetMultiplier里,即將發生發生改變時開始計時。然後在componentDidUpdate里,用Perf.stop()結束計時,然後列印渲染組件的時間跟浪費的時間。
componentDidUpdate() { Perf.stop() Perf.printInclusive() Perf.printWasted() } resetMultiplier() { Perf.start() this.setState({ multiplier: 2 }) }
當我們點擊按鈕,可以看到控制台列印出下面的資訊:

由控制台的數據可以看出,App用了90.59ms渲染,其中渲染ListItem的時間為55ms,渲染了10000次,其中有5000次是浪費的,因為這部分頁面的內容完全沒有更新的改動。
如何修復
既然是不需要渲染,那就要阻止它的渲染。React給我們提供了一個方法shouldComponentUpdate(),當這個方法返回true的時候,需要重新渲染,false的時候不需要(默認是true).
在這個栗子中,只要text的值不變,就不需要重新渲染。所以,可以這樣改寫ListItem 的shouldComponentUpdate
import React, { Component } from 'react' export default class ListItem extends Component { shouldComponentUpdate(nextProps, nextState) { return nextProps.text !== this.props.text } render() { let { text } = this.props return <li>{text}</li> } }
在重新點擊一下按鈕,在控制台可以發現

App總的渲染時間降到了62.14ms,並且ListItem只重新渲染了5000個節點,完全消除了浪費的渲染。
對於上面的寫法,React提供了一個新的組件PureComponent來做這件事,它會自動淺對比props/state,當兩者相同的時候不渲染節點。所以,listItem又可以改寫成
import React, { PureComponent } from 'react' export default class ListItem extends PureComponent { render() { let { text } = this.props return <li>{text}</li> } }
跑一跑程式碼

通過控制台可以看到達到的效果是一樣的(有點誤差是正常的)。
這裡再安利一個可以發現應用里是否存在不該重新渲染的節點工具:why-did-you-update
使用方法
npm i --save-dev why-did-you-update import React from 'react' if (process.env.NODE_ENV !== 'production') { const {whyDidYouUpdate} = require('why-did-you-update') whyDidYouUpdate(React) }
然後點擊按鈕看控制台

可以看到不應該重新渲染的節點出現了Value did not change. Avoidable re-render!的警告,是不是很實用!
注意的點
PureComponent只會淺比較,所以不適合用於深層嵌套的對象。同時,PureComponent不僅僅會跳過自己的重新渲染,還會跳過它所有子節點的,所以要注意,用它的時候是最好沒有子節點並且不依賴於global state的展示型組件。
與Staleless的關係
不知道有沒有人跟我有這樣的疑問,無狀態組件跟純凈組件有什麼不同?這裡做一個區分:
無狀態組件只是作為一個展示組件,它的好處是:
- 易復用,易測試
- 與邏輯處理數據層解耦,一般來說,app里有越多無狀態組件越好,這說明邏輯處理都在上層,例如redux 中處理,這樣可以在不渲染的前提下,測數據邏輯。
壞處:
- 沒有生命周期,沒辦法用shouldComponentUpdate阻止重新渲染,這也就是說,它沒有幫助我們提高性能的作用,這也是它跟PureComponent最大的不同。
關於如何在實際中使用這兩個組件,還要根據具體的實際情況來選擇~
總結
綜上可以看出,減少不必要的重新渲染對於提升我們的性能有很大的意義。我個人覺得,在實際中,用Perf跟why-did-you-update兩個工具已經可以很好幫我們判斷哪部分不需要重新渲染,我們可以根據結果做出優化。
遺留點
PureComponent那麼好用,但是使用PureComponent是有條件的呀~
由於PureComponent只是做了一個淺比較,所以深層嵌套的對象跟數組都是比不出來的,可能會導致需要渲染的地方沒有重新渲染的錯誤展示。
那麼淺比較又是什麼呢?下篇文章我們來繼續探索
參考鏈接:
1、https://60devs.com/pure-component-in-react.html