react性能優化

初識react只是爲了儘快完成項目,後期進行代碼審查時候發現有不少地方須要優化,所以作了個小結。html

  1. Code Splitting
  2. shouldComponentUpdate避免重複渲染
  3. 使用不可突變數據結構
  4. 組件儘量的進行拆分、解耦
  5. 列表類組件優化
  6. bind函數優化
  7. 不要濫用props
  8. ReactDOMServer進行服務端渲染組件

Code Splitting

Code Splitting 能夠幫你「懶加載」代碼,若是你沒辦法直接減小應用的體積,那麼不妨嘗試把應用從單個 bundle 拆分紅單個 bundle + 多份動態代碼的形式。
webpack提供三種代碼分離方法,詳情見webpack官網node

  • 入口起點:使用 entry 配置手動地分離代碼。
  • 防止重複:使用 SplitChunks 去重和分離 chunk。
  • 動態導入:經過模塊的內聯函數調用來分離代碼。

在此,主要了解一下第三種動態導入的方法。
一、例如能夠把下面的import方式react

import { add } from './math';
console.log(add(16, 26));

改寫成動態 import 的形式,讓首次加載時不去加載 math 模塊,從而減小首次加載資源的體積。webpack

import("./math").then(math => {
  console.log(math.add(16, 26));
});

二、例如引用react的高階組件react-loadable進行動態import。git

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-componentgithub

shouldComponentUpdate避免重複渲染

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

在一些狀況下,你的組件能夠經過重寫這個生命週期函數shouldComponentUpdate來提高速度, 它是在從新渲染過程開始前觸發的。 這個函數默認返回true,可以使React執行更新。chrome

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

clipboard.png

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

  • C1根節點,綠色SCU、紅色vDOMEq,表示須要更新。
  • C2節點,紅色SCU,表示不須要更新,同時C四、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 一直不會發生渲染。
避免此類問題最簡單的方式是避免使用值可能會突變的屬性或狀態,如:

一、數組使用concat,對象使用Object.assign()

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

二、ES6支持數組或對象的spread語法

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

三、使用不可突變數據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種方式:
一、constructor綁定

constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this); //構造函數中綁定
}
//而後能夠
<p onClick={this.handleClick}>

二、使用時綁定

<p onClick={this.handleClick.bind(this)}>

三、使用箭頭函數

<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掛起。

clipboard.png

相關文章
相關標籤/搜索