1. 程式人生 > >你所要掌握的最簡單基礎的React渲染優化

你所要掌握的最簡單基礎的React渲染優化

一、React的渲染機制

要掌握一兩項React-render優化的方法不難,但是非常重要.無論是在實際專案中的一個小細節,還是迎合'面試官'的口味

1.1 觸發Render

我們知道React要更新檢視,必須要觸發Render.而這往往是影響效能最重要的一步(因為操作了dom).而React之所以這麼出色,正是因為佔其主導地位的diff演算法採用了虛擬DOM(React V-dom),使得渲染效能大大提升。

即便如此,我們在開發的時候也應該要注意一些效能的優化,比如避免無意義的render

那麼,觸發render的條件有哪些呢?

  1. 首次載入元件
  2. 使用了setState(更新了Props)
  3. Props更新了(父級傳給子級的值改變了)

我們完全可以避免2.3情況導致的一些效能問題

1.2 React Diff

雖然說React高效的Diff演算法完美結合了虛擬DOM,讓使用者可以'無限制'重新整理而不需要考慮任何效能問題. 但diff演算法還是需要一定的時間,如果你不在意觸發render的細節,專案模組大了起來,自然就會影響效能了.

1.3 不做任何優化的例子

我嘗試著從實現以下功能:
  1. 不更新state的值,但是呼叫了setState方法
  2. 傳給子級的值不改變,即子級props實際上是沒變化的
// 父級.js
import React from 'react'
import MockChild from './child/index.js'
export default class Demo5 extends React.Component{
    constructor(args){
        super(args)
        
        this.state = {
            'mockNum': 0
        }
    }
    handleClick(){
        this.setState({
            'mockNum': 0,
        })
    }
    render(){
        console.log('父級state ==============>', this.state)
        return(
            <div>
                <button onClick={this.handleClick.bind(this)}>點選</button>
                <MockChild  mockNum={this.state.mockNum}/>
            </div>
        )
    }
}

//子元件
render(){
    console.log('子級Props =============>', this.props)
    return(
        <div></div>
    )
}

我們反覆點選按鈕,雖然state的值並沒有任何變化,但是我們看列印的結果!

render重複渲染了!

可能會疑問,我在專案並不會做這麼一種無用功的!但實際上,當一個元件邏輯複雜起來之後,會產生各種各樣的情況.比如:

比如一個元件的中有個包含onChange事件的input,當我改變該輸入框內容的時候呼叫了setState,渲染檢視的時候也會觸發子元件的重新render.但我們明明沒有做任何和子元件有聯絡的操作 例如上面的例子:

//父元件.js
state = {
    'mockValue': '123'    
}
handleChange(e){
    this.setState({
        'value': '123',
    })
}

//render
<input onChange={this.handleChange.bind(this)} />
<MockChild />

/*
* 子元件不做變化
*/

很不爽,真的!必須給安排掉.

二、基礎的React優化

2.1 生命週期: shouldComponentUpdate

可能沒聽過shouldComponentUpdate,我們簡單介紹一下它的執行週期

可以看到它是在render渲染之前觸發的,只要我們在這裡加以判斷就可以有效阻止'無用'的render觸發.當然你說componentWillReceiveProps也可以,當然!但是它只有props的更新判斷,而有時候我們也不能放過未更改的state!

shouldComponentUpdate(nextProps, nextState){
    if(nextState !== this.state && nextProps !== this.props) return true;
    return false
}

這麼簡短的程式碼就可以解決冗餘的render觸發.當然有時候規模大了之後,就需要具體到某一個值

nextState.xxx !== this.state.xxx && ...

2.2 PureComponent

先介紹PureComponent的用法,實在是太簡便了
import React, { PureComponent } from 'react'

export default class Demo5 extends PureComponent{

}

實際上就是把React.Component代替成PureComponent.

可能會疑問那PureComponent應該是一個外掛吧?為什麼就在react包裡?其實它是官方在React15.3提出的一個'純淨的元件'

在版本允許的情況下,還是建議使用PureComponent,既能優化render次數,也能減少shouldComponentUpdate的程式碼。但是PureComponent只是執行了淺比較

什麼是淺比較呢?

我們先來看看原始碼

if (this._compositeType === CompositeTypes.PureClass) {
  shouldUpdate = !shallowEqual(prevProps, nextProps)
  || !shallowEqual(inst.state, nextState);
}

可以看到判斷主要是通過shallowEqual方法執行的(即可以判斷state,也可以判斷props)

shallowEqual具體作用是什麼呢?實際上它使用了ES6的Object.keys.只是做了以下這麼幾個判斷:

  1. 新的和舊的props(state)是否兩者都有相同的key?
  2. 引用是否改變

第二種可能有點難以理解,什麼是引用是否改變?

簡單地解釋就是,是否有新的元素參與改變

舉個官方常用例子:

class App extends PureComponent {
  state = {
    items: [1, 2, 3]
  }
  handleClick = () => {
    const { items } = this.state;
    items.pop();
    this.setState({ items });
  }
  render() {
    return (<div>
      <ul>
        {this.state.items.map(i => <li key={i}>{i}</li>)}
      </ul>
      <button onClick={this.handleClick}>delete</button>
    </div>)
  }
}

可以看到,我們點選delete的時候,雖然items陣列執行了pop()方法,刪除最後一項!但是li標籤卻沒變少!那是因為shallowEqual根本不吃你這套.它認為items還是同一個引用,所以給我true!在通過!反過來就是false

那要如果改變引用呢? 我們可以這樣嘗試.(這也是解決淺比較常用的一個辦法)

this.setState({ 
    items: [].concat(items) 
});

當然,大多數情況我們都可以通過PureComponent解決啦!實在不行,還可以通過componentWillReceiveProps進一步判斷呢!