1. 程式人生 > >React效能優化——程式碼篇

React效能優化——程式碼篇

如果使用工具檢測出頁面浪費的渲染次數太多,就需要檢查程式碼是否寫法上有問題了。雖然 Virtual DOM 演算法可以避免大多無效的真實 DOM 操作,但還是會浪費時間在計算不會改變的虛擬 DOM 上,也就是執行了 render 函式,但發現並沒有任何改變。

轉載https://wulv.site/2017-07-02/react-perf-code.html

key

key 屬性作為列表的子元件的身份標識,是不能重複的。不寫 key 屬性, React 會在瀏覽器控制檯報 warning ,有時候我們為了去除這個警告,直接使用陣列的索引做 key ,這是不太好的。

123456789 class
App extends Component
{ render() { return (<div> {this.props.list.map((value, index) => { <Child key={index} data={value} /> })} </div>); }}

如果 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沒有成為效能瓶頸之前,這都只是程式碼寫法上的事。