1. 程式人生 > >淺談react效能優化的方法

淺談react效能優化的方法

這篇文章主要介紹了淺談react效能優化的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

React效能優化思路

軟體的效能優化思路就像生活中去看病,大致是這樣的:

使用工具來分析效能瓶頸(找病根)

嘗試使用優化技巧解決這些問題(服藥)

使用工具測試效能是否確實有提升(療效確認)

初識react只是為了儘快完成專案,後期進行程式碼審查時候發現有很多地方需要優化,因此做了個小結。

Code Splitting

shouldComponentUpdate避免重複渲染

使用不可突變資料結構

元件儘可能的進行拆分、解耦

列表類元件優化

bind函式優化

不要濫用props

ReactDOMServer進行服務端渲染元件

Code Splitting

Code Splitting 可以幫你“懶載入”程式碼,如果你沒辦法直接減少應用的體積,那麼不妨嘗試把應用從單個 bundle 拆分成單個 bundle + 多份動態程式碼的形式。

webpack提供三種程式碼分離方法,詳情見webpack官網

入口起點:使用 entry 配置手動地分離程式碼。

防止重複:使用 SplitChunks 去重和分離 chunk。

動態匯入:通過模組的行內函數呼叫來分離程式碼。

在此,主要了解一下第三種動態匯入的方法。

1、例如可以把下面的import方式

import { add } from './math'``;
console.log(add(16, 26));
改寫成動態 import 的形式,讓首次載入時不去載入 math 模組,從而減少首次載入資源的體積。
[?]瞭解更多
)
import(``"./math"``).then(math => {
console.log(math.add(16, 26));
});
2、例如引用react的高階元件react-loadable進行動態import。

import Loadable from 'react-loadable'``;
import Loading from

'./loading-component'``;
const LoadableComponent = Loadable({
loader: () => import(``'./my-component'``),
loading: Loading,
});
export default class App extends React.Component {
render() {
return <LoadableComponent/>;
}
}
上面的程式碼在首次載入時,會先展示一個 loading-component,然後動態載入 my-component 的程式碼,元件程式碼載入完畢之後,便會替換掉 loading-component
shouldComponentUpdate避免重複渲染

當一個元件的props或者state改變時,React通過比較新返回的元素和之前渲染的元素來決定是否有必要更新實際的DOM。當他們不相等時,React會更新DOM。

在一些情況下,你的元件可以通過重寫這個生命週期函式shouldComponentUpdate來提升速度, 它是在重新渲染過程開始前觸發的。 這個函式預設返回true,可使React執行更新。

為了進一步說明問題,引用官網的圖解釋一下,如下圖( SCU表示shouldComponentUpdate,綠色表示返回true(需要更新),紅色表示返回false(不需要更新);vDOMEq表示虛擬DOM比對,綠色表示一致(不需要更新),紅色表示發生改變(需要更新)):

image
根據渲染流程,首先會判斷shouldComponentUpdate(SCU)是否需要更新。如果需要更新,則呼叫元件的render生成新的虛擬DOM,然後再與舊的虛擬DOM對比(vDOMEq),如果對比一致就不更新,如果對比不同,則根據最小粒度改變去更新DOM;如果SCU不需要更新,則直接保持不變,同時其子元素也保持不變。

C1根節點,綠色SCU、紅色vDOMEq,表示需要更新。

C2節點,紅色SCU,表示不需要更新,同時

C4、C5作為其子節點也不需要檢查更新。

C3節點,綠色SCU、紅色vDOMEq,表示需要更新。

C6節點,綠色SCU、紅色vDOMEq,表示需要更新。

C7節點,紅色SCU,表示不需要更新。

C8節點,綠色SCU,表示React需要渲染這個元件;綠色vDOMEq,表示虛擬DOM一致,不更新DOM。

因此,我們可以通過根據自己的業務特性,過載shouldComponentUpdate,只在確認真實DOM需要改變時,再返回true。一般的做法是比較元件的props和state是否真的發生變化,如果發生變化則返回true,否則返回false。引用官網的案例。

`class CounterButton extends React.Component {`
`constructor(props) {`
`super``(props);`
`this``.state = {count: 1};`
`}`
`shouldComponentUpdate(nextProps, nextState) {`
`if` `(``this``.props.color !== nextProps.color) {`
`return` `true``;`
`}`
`if` `(``this``.state.count !== nextState.count) {`
`return` `true``;`
`}`
`return` `false``;`
`}`
`render() {`
`return` `(`
`<button`
`color={``this``.props.color}`
`onClick={() =>` `this``.setState(state => ({count: state.count + 1}))}>`
`Count: {``this``.state.count}`
`</button>`
`);`
`}`
`}`

在以上程式碼中,shouldComponentUpdate只檢查props.color和state.count的變化。如果這些值沒有變化,元件就不會更新。當你的元件變得更加複雜時,你可以使用類似的模式來做一個“淺比較”,用來比較屬性和值以判定是否需要更新元件。這種模式十分常見,因此React提供了一個輔助物件來實現這個邏輯 - 繼承自React.PureComponent。

大部分情況下,你可以使用React.PureComponent而不必寫你自己的shouldComponentUpdate,它只做一個淺比較。但是當你比較的目標為引用型別資料,淺比較會忽略屬性或狀態突變的情況,此時你不能使用它,此時你需要關注下面的不可突變資料。

附:資料突變(mutated)是指變數的引用沒有改變(指標地址未改變),但是引用指向的資料發生了變化(指標指向的資料發生變更)。例如const x = {foo:‘foo’}。x.foo=‘none’ 就是一個突變。

使用不可突變資料結構

引用官網中的例子解釋一下突變資料產生的問題。例如,假設你想要一個ListOfWords元件來渲染一個逗號分隔的單詞列表,並使用一個帶了點選按鈕名字叫WordAdder的父元件來給子列表新增一個單詞。以下程式碼並不正確:

`class ListOfWords extends React.PureComponent {`
`render() {`
`return` `<div>{``this``.props.words.join(``','``)}</div>;`
`}`
`}`
`class WordAdder extends React.Component {`
`constructor(props) {`
`super``(props);`
`this``.state = {`
`words: [``'marklar'``]`
`};`
`this``.handleClick =` `this``.handleClick.bind(``this``);`
`}`
`handleClick() {`
`// 這段內容將會導致程式碼不會按照你預期的結果執行`
`const words =` `this``.state.words;`
words.push(``'marklar'``);`
`this``.setState({words: words});`
`}`
`render() {`
`return` `(`
`<div>`
`<button onClick={``this``.handleClick} />`
`<ListOfWords words={``this``.state.words} />`
`</div>`
`);`
`}`
`}`

導致程式碼無法正常工作的原因是 PureComponent 僅僅對 this.props.words的新舊值進行“淺比較”。在words值在handleClick中被修改之後,即使有新的單詞被新增到陣列中,但是this.props.words的新舊值在進行比較時是一樣的(引用物件比較),因此 ListOfWords 一直不會發生渲染。
避免此類問題最簡單的方式是避免使用值可能會突變的屬性或狀態,如:
1、陣列使用concat,物件使用Object.assign()

`handleClick() {`
`this``.setState(prevState => ({`
`words: prevState.words.concat([``'marklar'``])`
`}));`
`}`
`// 假設我們有一個叫colormap的物件,下面方法不汙染原始物件`
`function` `updateColorMap(colormap) {`
`return` `Object.assign({}, colormap, {right:` `'blue'``});`
`}`

2、ES6支援陣列或物件的spread語法

`handleClick() {`
`this``.setState(prevState => ({`
`words: [...prevState.words,` `'marklar'``],`
`}));`
`};`
`function` `updateColorMap(colormap) {`
`return` `{...colormap, right:` `'blue'``};`
`}`

3、使用不可突變資料immutable.js

immutable.js使得變化跟蹤很方便。每個變化都會導致產生一個新的物件,因此我們只需檢查索引物件是否改變。
`const SomeRecord = Immutable.Record({ foo:` `null` `});`
`const x =` `new` `SomeRecord({ foo:` `'bar'` `});`
`const y = x.set(``'foo'``,` `'baz'``);`
`x === y;` `// false`

在這個例子中,x突變後返回了一個新的索引,因此我們可以安全的確認x被改變了。

不可突變的資料結構幫助我們輕鬆的追蹤物件變化,從而可以快速的實現shouldComponentUpdate。

具體如何使用可參考下面文章:Immutable 詳解及 React 中實踐

元件儘可能的進行拆分、解耦

元件儘可能的細分,比如一個input+list元件,可以將list分成一個PureComponent,只在list資料變化時更新。否則在input值變化頁面重新渲染的時候,list也需要進行不必要的DOM diff。
列表類元件優化

key屬性在元件類之外提供了另一種方式的元件標識。通過key標識,在元件發生增刪改、排序等操作時,可以根據key值的位置直接調整DOM順序,告訴React 避免不必要的渲染而避免效能的浪費。
例,對於一個基於排序的元件渲染:

`var` `items = sortBy(``this``.state.sortingAlgorithm,` `this``.props.items);`
`return` `items.map(``function``(item){`
`return` `<img src={item.src} />`
`});`

當順序發生改變時,React 會對元素進行diff操作,並改img的src屬性。顯示,這樣的操作效率是非常低的。這時,我們可以為元件新增一個key屬性以唯一的標識元件:
return <img src={item.src} key={item.id} />
增加key後,React就不是diff,而是直接使用insertBefore操作移動元件位置,而這個操作是移動DOM節點最高效的辦法。
bind函式

繫結this的方式:一般有下面3種方式:

1、constructor繫結

`constructor(props) {`
`super``(props);`
`this``.handleClick =` `this``.handleClick.bind(``this``);` `//建構函式中繫結`
`}`
`//然後可以`
`<p onClick={``this``.handleClick}>`

2、使用時繫結

<``p onClick={this.handleClick.bind(this)}>
3、使用箭頭函式

<``Test click={() => { this.handleClick() }}/>
以上三種方法,第一種最優。

因為第一種建構函式只在元件初始化的時候執行一次,

第二種元件每次render都會執行

第三種在每一次render時候都會生成新的箭頭函式。例:Test元件的click屬性是個箭頭函式,元件重新渲染的時候Test元件就會

因為這個新生成的箭頭函式而進行更新,從而產生Test元件的不必要渲染。

不要濫用props

props儘量只傳需要的資料,避免多餘的更新,儘量避免使用{…props}
ReactDOMServer進行服務端渲染元件

為了使用者會更快速地看到完整渲染的頁面,可以採用服務端渲染技術,在此瞭解一下ReactDOMServer。

為了實現SSR,你可能會用nodejs框架(Express、Hapi、Koa)來啟動一個web伺服器,接著呼叫 renderToString 方法去渲染你的根元件成為字串,最後你再輸出到 response。

// using Express
import { renderToString } from "react-dom/server"``;
import MyPage from "./MyPage"``;
app.get(``"/"``, (req, res) => {
res.write(``"<!DOCTYPE html><html><head><title>My Page</title></head><body>"``);
res.write(``"<div id='content'>"``);
res.write(renderToString(<MyPage/>));
res.write(``"</div></body></html>"``);
res.end();
});

客戶端使用render方法來生成HTML

import ReactDOM from 'react-dom'``;
import MyPage from "./MyPage"``;
ReactDOM.render(<MyPage />, document.getElementById(``'app'``));

react效能檢測工具

react16版本之前,我們可以使用react-addons-perf工具來檢視,而在最新的16版本,我們只需要在url後加上?react_pref。

首先來了解一下react-addons-perf。

react-addons-perf這是 React 官方推出的一個性能工具包,可以打印出元件渲染的時間、次數、浪費時間等。

簡單說幾個api,具體用法可參考官網:

Perf.start() 開始記錄

Perf.stop() 結束記錄

Perf.printInclusive() 檢視所有設計到的元件render

Perf.printWasted() 檢視不需要的浪費元件render

再來了解一下,react16版本的方法,在url後加上?react_pref,就可以在chrome瀏覽器的performance,我們可以檢視User Timeing來檢視元件的載入時間。點選record開始記錄,注意記錄時長不要超過20s,否則可能導致chrome掛起。


image
以上就是本文的全部內容,希望對大家的學習有所幫助。