React學習以前端開發性能優化

1. 單個react組件性能優化

1.1 render裏面儘可能減小新建變量和bind函數的使用,儘可能減小傳遞參數的數量

render中綁定函數,無非就是下面三種:javascript

render() {
  return (
    <div className='app'>
      <span onClick={this.handleClick}>1</span>
      <span onClick={this.handleClick.bind(this)}>2</span>
      <span onClick={()=>this.handleClick()}>3</span>
    </div>
  )
}

第一種是在構造函數中綁定this,第二種是在render()函數裏面綁定this,第三種就是使用箭頭函數,上述方法都能實現this的綁定。java

可是哪種方法的性能最好,是咱們要考慮的問題。毫無疑問第一種的性能最好react

第一種方法,構造函數每渲染一次便會執行一遍;算法

第二種方法,在每次render()的時候都會從新執行一遍函數;編程

第三種方法,每一次render()的時候,都會生成一個新的箭頭函數,即便兩個箭頭函數的內容是同樣的。數組

react判斷是否須要進行render淺層比較,簡單來講就是經過===來判斷的,若是state或者prop的類型是字符串或者數字,只要值相同,那麼淺層比較就會認爲其相同;瀏覽器

可是若是前者的類型是複雜的對象的時候,咱們知道對象是引用類型,淺層比較只會認爲這兩個prop是否是同一個引用,若是不是,哪怕這兩個對象中的內容徹底同樣,也會被認爲是兩個不一樣的prop安全

舉個例子:性能優化

當咱們給組件App名爲styleprop賦值;數據結構

<App style={{ color:"green" }}

使用這種方法,每一次渲染都會被認爲是一個style這個prop發生了變化,由於每一次都會產生一個對象給style

若是想要讓react渲染的時候認爲先後對象類型prop相同,則必需要保證prop指向同一個javascript對象,改進以下:

const appStyle = { color: "red" }; //這個初始化只執行一次,不要放在render中,能夠放在構造函數中
<App style={appStyle} />

1.2 定製shouldComponentUpdate函數

生命週期函數shouldComponentUpdate是決定react組件何時可以從新渲染的函數,可是這個函數默認的實現方式就是簡單的返回一個true。也就是說,默認每次更新的時候都要調用所用的生命週期函數,包括render函數,從新渲染。

看看下面這個例子:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count = 2,
      name = 'apple',
    }
    this.handleClick = this.handleClick.bind(this);
    this.handleName = this.handleName.bind(this);
  }
  this.handleClick() {
  // ...
  }
  this.handleName() {
  // ...
  }
  render() {
    return (
      <div className='app'>
        <span>數量,{this.state.count}</span>
        <button onClick={this.handleClick}>改變數量</button>
        <button onClick={this.handleName}>改變名字</button>
        <Child title={this.state.name}></Child>
      </div>
    );
  }
}


class Child extends React.Component {
  render() {
    console.log('render了一次');
    return (
      <h3>我想吃,{this.props.title}</h3>
    );
  }
}

咱們寫了兩個組件,AppChild組件,並寫兩個方法,一個改變App中的count的值,一個是改變name,咱們在Childrender中打印了每次是否執行。

不出意外,雖然Child組件裏的title值沒有改變,可是仍是render了。

爲了進一步優化這個問題,咱們這樣改Child組件:

class Child extends React.Component {
  shouldComponentUpdate(nextProps,nextState) {
    if(nextProps.title == this.props.title) {
      return false;
    }
    return true;
  }

  render() {
    console.log('render了一次');
    return (
      <h3>我想吃,{this.props.title}</h3>
    );
  }
}

只有當Childtitle值發生改變的時候,組件纔會去render

在最新的react中,react給咱們提供了React.PureComponent,官方也在早期提供了名爲react-addons-pure-render-mixin插件來從新實現shouldComponentUpdate生命週期方法。

class Child extends React.PureComponent {
  // shouldComponentUpdate(nextProps,nextState) {
  //   if(nextProps.title == this.props.title) {
  //     return false;
  //   }
  //   return true;
  // }

  render() {
    console.log('render了一次');
    return (
      <h3>我想吃,{this.props.title}</h3>
    );
  }
}

經過上述的方法的效果也是和咱們先前定製shouldComponentUpdate的效果是一致的。

可是咱們要注意的是,這裏的PureRender是淺比較的,由於深比較的場景是至關昂貴的。因此咱們要注意咱們在1.1中說到的一些注意點:不要直接爲props設置對象或者數組不要將方法直接綁定在元素上,由於其實函數也是對象。

1.3 Immutable.js

javascript中的對象通常都是可變的,由於使用了引用賦值,新的對象簡單的引用了原始對象,改變新對象將影響到原始對象。

舉個例子:

student = { age : 1 };
school = student;
school.age = 2;

當咱們給school.age賦值後,會發現student.a也變成了2,雖然咱們能夠經過深拷貝與淺拷貝解決這個問題,可是這樣作很是的昂貴,對cpu和內存會形成浪費。

這裏就須要用到Immutable,經過Immutable建立的Immutable Data一旦被建立,就不能再更改。對Immutable對象進行修改、添加或刪除操做,都會返回一個新的Immutable對象。

下面是三個比較重要且用到的數據結構

  • Map:鍵值對集合,對應Object,ES6中也有專門的Map對象
  • List:有序可重複列表,對應於Array
  • ArraySet:有序且不可重複的列表

咱們能夠看一個例子:

使用Map生成一個immutable對象:

import { Map, is } from 'immutable';

let a = Map({
  'name': 'apple',
  'list': Map({name: 'orange'})
})

let b = a.set('name','banana');

console.log(a.get('course') === b.get('course')); // 返回true
console.log(a === b); // 返回false

Immutable.is 比較的是兩個對象的 hashCodevalueOf(對於JavaScript對象)。因爲immutable內部使用了Trie數據結構來存儲,只要兩個對象的 hashCode 相等,值就是同樣的。這樣的算法避免了深度遍歷比較,性能很是好。

Immutable優勢

  • 減小內存的使用
  • 併發安全
  • 下降項目的複雜度
  • 便於比較複雜數據,定製shouldComponentUpdate方便
  • 時間旅行功能
  • 函數式編程

Immutable缺點

  • 學習成本
  • 庫的大小(建議使用seamless-immutable
  • 對現有項目入侵嚴重
  • 容易與原生的對象進行混淆

2. 多個react組件性能優化

react組件在裝載過程當中,react經過在render方法在內存中產生一個樹形結構,樹上的節點表明一個react組件或者原生的Dom元素,這個樹形結構就是咱們所謂的Vitural Domreact根據這個來渲染產生瀏覽器的Dom樹。

react在更新階段對比原有的Vitural Dom和新生成的Vitural Dom,找出不一樣之處,在根據不一樣來渲染Dom樹。

react爲了追求高性能,採用了時間複雜度爲O(N)來比較兩個屬性結構的區別,由於要確切比較兩個樹形結構,須要經過O(N^3),這會下降性能。

  • 節點類型不一樣
// A組件
<div>
  <Todos />
</div>

// B組件
<span>
  <Todos />
</span>

咱們想把A組件更新成B組件,react在作比較的時候,發現最外面的根結點徹底不同,直接銷燬以前的<div>節點,包括裏面的子節點也一併銷燬,這是一個巨大的浪費,可是爲了不O(N^3)的時間複雜度,只能採用這種方式。

因此在開發過程當中,咱們應該儘可能避免上面的狀況,不要將包裹節點的類型隨意改變。

  • 兩個節點類型同樣

這裏包括兩種狀況,一種是節點是Dom類型,還有一種react組件。

對於dom類型,咱們舉個例子:

// A組件
<div style={{color: 'red',fontSize:15}} className="welcome">
  Hello World!!!
</div>

// B組件
<div style={{color: 'green',fontSize:15}} className="react">
  Good Bye!!!
</div>

上述A和B組件的區別是文字、classNamestyle中的color發生改變,由於Dom元素沒變,React只會修改他變化的部分。

針對react組件類型,渲染無非就是再執行一遍組件實例的更新過程,最主要的就是定製shouldComponentUpdate

  • 多個子組件狀況

例子一:

// A
<ul>
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
</ul>

// B
<ul>
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
  <TodoItem text="Third" complete={false} />
</ul>

從A變到B,若是shouldComponentUpdate處理得當,咱們只須要更新裝載third的那一次就行。

咱們來看看下一個例子:

// A
<ul>
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
</ul>

// B
<ul>
  <TodoItem text="Zero" complete={false} />
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
</ul>

這裏由於react是採用O(n)的時間複雜度,因此會依次將textFirst的改成ZerotextSecond改成First,在最後再加上一個組件,textSecond。現存的兩個的text的屬性都被改變了,因此會依次渲染。

若是咱們這裏有100個實例,那麼就會發生100次更新。

這裏咱們就要用到Key

簡單來講,其實這一個Key就是react組件的身份證號。

咱們將上一個例子改爲以下,就能夠避免上面的問題了,react就可以知道其實B裏面的第二個和第三個組件其實就是A中的第一個和第二個實例。

// A
<ul>
  <TodoItem key={1} text="First" complete={false} />
  <TodoItem key={2} text="Second" complete={false} />
</ul>

// B
<ul>
  <TodoItem key={0} text="Zero" complete={false} />
  <TodoItem key={1} text="First" complete={false} />
  <TodoItem key={2} text="Second" complete={false} />
</ul>

不過如今,react也會提醒咱們不要忘記使用key,若是沒有加,瀏覽器中會報錯。

關於key的使用咱們要注意的是,這個key值要穩定不變的,就如同身份證號對於咱們是穩定不變的同樣。

一個常見的錯誤就是,拿數組的的下標值去當作key,這個是很危險的,代碼以下,咱們必定要避免

<ul>
  {
    todos.map((item, index) => {
      <TodoItem
        key={index}
        text={item.text}
        completed={item.completed}
      >
    })
  }
</ul>

未完待續...

相關文章
相關標籤/搜索