React效能優化——程式碼篇
如果使用工具檢測出頁面浪費的渲染次數太多,就需要檢查程式碼是否寫法上有問題了。雖然 Virtual DOM
演算法可以避免大多無效的真實 DOM
操作,但還是會浪費時間在計算不會改變的虛擬 DOM
上,也就是執行了 render
函式,但發現並沒有任何改變。
轉載https://wulv.site/2017-07-02/react-perf-code.html
key
key
屬性作為列表的子元件的身份標識,是不能重複的。不寫 key
屬性, React
會在瀏覽器控制檯報 warning
,有時候我們為了去除這個警告,直接使用陣列的索引做 key
,這是不太好的。
123456789 |
class |
如果 list
發生變化,比如從中間去除一條資料,兩次渲染之間 key
相同的子元件,React
認為他們是同一個元件,這樣很多個 Child
元件都改變了。最好使用一個唯一的value.id
,如果沒有 id
,使用value.name
key
不必須是數字,只要保證不重複的字元就可以了。
shouldComponentUpdate
在 React
生命週期裡, shouldComponentUpdate
表示元件是否需要被更新,我們可以做一些判斷,來優化元件效能,讓一些確定不會改變檢視的操作,直接不去計算。shouldComponentUpdate
在初始化時或者使用 forceUpdate
時是不被執行的。 shouldComponentUpdate
預設返回值是 true
,也就是元件一定會更新,如果返回值為 false
,會阻止後面 componentWillUpdate
、 render
、 componentDidUpdate
componentWillReceiveProps
裡有 setState
操作也會被阻止。
比如我們確定如果一個子元件的 name
和 tel
屬性不變,子元件就不需要更新:
1234567 | shouldComponentUpdate(nextProps) { const { data } = this.props; const { name, tel } = data; return name !== nextProps.data.name || tel !== nextProps.data.tel;} |
我們最好只傳遞 component
需要的 props
,如果傳得太多,或者層次傳得太深,都會加重 shouldComponentUpdate
裡面的資料比較負擔,因此,也請慎用<Component
{...this.props} />
操作。
123456789 | import pick from 'lodash/pick';class App extends Component { const props = pick(this.props, [max, min, onClick]) render() { return (<div> <NumberInput {...props} /> </div>); }} |
PureComponent
PureComponent
是一個很有效的優化,在前面的部落格專門有一篇來介紹:React
PureComponent 使用指南。
Stateless components
React v0.14
就添加了 functional
components
,他的行為很像只有一個 render
方法的 class
components
,但沒有生命週期方法,也沒有例項物件,所以不能夠使用 ref
。目前來說 functional
components
並沒有特別優化,在內部也是包裝在同一個類中。我們可以直接以函式方式使用,這樣可以優化一部分效能:
12345678910111213 | const Avatar = (props) => { return <img src={props.url} />;}// 元件方式使用render() { return (<div> <Avatar url={avatarUrl} /> </div>);// 函式方式使用render() { return (<div> {Avatar({ url: avatarUrl })} </div>); |
React
官方已經承諾,在未來會通過避免不必要的檢查和記憶體分配來對這些元件進行效能優化。
constant elements
我們可以將確定不變的html
程式碼抽離出來直接當做一個變數,寫在jsx
裡,會解析jsx
語法,生成React.createElement()
程式碼。如果提升成靜態元素,jsx
會直接把它們當做一個值,減少了解析過程:
1234567891011 | const _ref = <span>Hello World</span>;class MyComponent extends Component { render() { return ( <div className={this.props.className}> {_ref} </div> ); }} |
慎用setState
如果是和檢視無關的,但有變化的資料,不要放在 state
裡面,比如某個元件需要 mouseDown
時標記開始記錄, mouseUp
清除記錄,最好直接當做例項的一個屬性。這樣可以避免執行無效的 render
操作。
1234567891011121314 | class App extends Component { record = false onMouseDown = () => { this.record = true; } onMouseUp = () => { this.record = false; } render() { return (<div> <button onMouseDown={this.onMouseDown} onMouseUp={this.onMouseUp} /> <div/>); }} |
總結一下:render
函式裡面要用到的東西放props/state
(影響 view
),其他的不要放進去,寫成模組內的私有變數(跨例項共享)或者元件例項上的變數。
在React PureComponent
使用指南里,複雜狀態與簡單狀態不要共用一個元件那一段也提到了慎用setState
的原因。
慎用bind
Component
的 render
裡不使用 bind
繫結 this
,可以放在 constructor
裡繫結好,或者直接使用箭頭函式,如果要動態傳參,可以使用閉包,或者可以直接把處理函式傳入子元件,子組建時可以拿到引數,再執行父元件的處理函式就可以了。
1234567891011121314151617181920212223242526272829303132333435363738394041 | // 閉包class App extends Component { removeCharacter = index => () => { const { list } = this.state; list.splice(index, 1); this.setState({ list }); } render() { return (<div> {this.state.list.map((value, index) => <Child onClick={this.removeCharacter(index)} key={value.id} data={value} /> )} </div>); }}// 子元件處理class App extends Component { removeCharacter = index => { const { list } = this.state; list.splice(index, 1); this.setState({ list }); } render() { return (<div> {this.state.list.map((value, index) => <Child onClick={this.removeCharacter} index={index} key={value.id} data={value} /> )} </div>); }}class Child extends Component { handleClick = () => { const { index, onClick } =this.props; onClick(index); } render() { return <div onClick={this.handleClick}> {this.props.data} </div> }} |
如果每次都在 render
裡面的 jsx
去 bind
這個方法,會消耗效能,因為每次bind
都會返回一個新函式,重複建立靜態函式肯定是不合適的(閉包也是這樣,但bind
內部有一系列的演算法,比閉包複雜多了)。
關於bind
效能問題可以檢視以下資料:
總的來說,目前瀏覽器已經足夠快了,在bind
沒有成為效能瓶頸之前,這都只是程式碼寫法上的事。