首頁  >  文章  >  web前端  >  有關React組件效能優化詳細講解

有關React組件效能優化詳細講解

亚连
亚连原創
2018-06-02 09:13:181374瀏覽

這篇文章主要介紹了淺談React組件之效能優化,現在分享給大家,也給大家做個參考。

高德納: "我們應該忘記忽略很小的性能優化,可以說97%的情況下,過早的優化是萬惡之源,而我們應該關心對性能影響最關鍵的另外3 %的程式碼。"

不要將效能優化的精力浪費在對整體效能提高不大的程式碼上,而對效能有關鍵影響的部分,優化並不太早。因為,對效能影響最關鍵的部分,往往涉及解決方案核心,決定整體的架構,將來要改變的時候牽扯更大。

1. 單一React元件的效能最佳化

#React利用Virtual DOM來提升渲染效能,雖然每一次頁面更新都是最元件的從新渲染,但是並不是將之前的渲染內容全部拋棄重來,借助Virtual DOM,React能夠計算出對DOM樹的最少修改,這就是React默認情況下渲染都很迅速的秘訣;

不過,雖然Virtual DOM能夠將每次DOM操作量減少到最小,但,計算和比較Virtual DOM依然是一個複雜的過程;

當然,如果能夠在開始計算Virtual DOM之前就判斷渲染的結果不會有變化,那麼就可以不進行Virtual DOM計算和比較,速度就會更快。

2.shouldComponentUpdate的預設實作方式

既然可以對元件在開始計算Virtual DOM之前判斷渲染結果不會有變化時,阻止渲染的進行,從而提升效能,那麼我們自然想到使用shouldComponentUpdate(nextProp,nextState)

shouldComponentUpdate函數在render函數之前調用,決定「什麼時候不需要從新渲染」;

即傳回一個布林值,決定更新是否進行下去,預設回傳true,若回傳false則中斷更新;

shouldComponentUpdate(nextProp,nextState){
  return (nextProp.completed !== this.props.completed) ||
    (nextProp.text !== this.props.text)
}

其中nextProps為此次更新傳入的props,對於這個元件,影響渲染內容的prop只有completed和text,只要確保這兩個prop沒有變化,shouldComponentUpdate就可以返回false阻止沒必要的更新

#但是,上述的比較只是'淺層比較',如果類型是基本類型,只要值相同,那麼“淺層比較”

也會認為二者相同:

那,如果prop的型別是複雜的物件怎麼辦?

對於複雜對象,'淺層比較'的方式只看這兩個prop是不是同一個對象的引用,如果不是,哪怕對像中的內容完全一樣也會認為是不同的兩個prop。那麼使用“深層比較”:但對物件的結構是無法預測的,如果遞歸對每個字段都進行“深層比較”,不光會讓程式碼更加複雜,也可能會造成效能問題。

所以,要判斷前後的物件類型的prop是相同的,就必須保證prop是指向同一個JavaScript物件:

<Foo styleProp = {{color: "red"}}>

要避免使用上面的傳入方式,應為每次渲染都會重新建立{color: "red"}對象,引用位址每次都不同,將導致每次的styleProp都不同。

const footStyle = {color: "red"};//确保这个初始化只执行一次,不要放在render函数中
<Foo styleProp = {footStyle}>

使用'單例模式'確保傳入的styleProp指向同一個物件

如果是函數呢?

<Foo onToggle={() => onToggleTodo(item.id)}/>

應該避免使用上面的函數傳遞模式,因為這裡賦值的是一個匿名函數,而且是在賦值的時候產生的,也是說每次渲染都會產生一個新的函數,這就是問題所在。

如果要傳遞的prop很多呢?

恩~~用React-Redux的話,有對shouldComponentUpdate的預設實作。

3. 對多個React元件的效能最佳化

#當一個React元件被裝載、更新和卸載時,元件的一個序列生命週期函數會被呼叫。不過,這些生命週期函數是針對一個特定的React元件函數,在一個應用程式中,從上而下有許多React元件組合起來,它們之間的渲染過程要更加複雜。

同樣一個元件的渲染過程也要考慮三個流程:裝載階段、更新階段、卸載階段

對於裝載階段,元件無論如何都要徹底渲染一次,從這個React元件往下的所有子元件,都要經歷一遍React元件的裝載生命
週期,所以並沒有太多優化的事情可做。

對於卸載階段,只有一個生命週期函數componentWillUnmount,這個函數只是清理componentDidMount新增的事件處理監聽等收尾工作,所以,也沒有什麼可優化的空間;

4. React更新階段的調和(Reconciliation)過程

在元件更新過程,會建立更新Virtual DOM,並將其與先前的Virtual DOM進行比較,從而找出不同之處,使用最少的DOM操作進行更新

调和过程:即React更新中对Virtual DOM找不同的过程,通常对比两个N个节点的树形结构的算法,时间复杂度是O(n*3),如果直接

使用默认对比,节点过多的话,需要操作的数量太多,而React不可能采用这种算法;

React实际采用的算法时间复杂度是O(N)(时间复杂度只是对一个算法最好和最差情况下需要的指令操作数量级的估量)

React的Reconciliation算法并不复杂,首先检查两个树形的根节点的类型是否相同,根据相同或者不同有不同的处理方式:

节点类型不同的情况

如果树形节点的类型不相同,那就意味着改动很大,直接认为原来的那个树形结构已经没用,可以扔掉,需要从新构建DOM树,原有的树形上的React组件便会经历“卸载”的生命周期;

也就是说,对于Virtual DOM树这是一个“更新”过程,但是却可能引发这个树结构上某些组件的“装载”和“卸载”过程
如:

更新前

 <p>
  <Todos />
 </p>

我们想要更新成这样:

 <span>
   <Todos />
 </span>

>1. 那么在作比较的时候,一看根节点原来是p,新的是span,类型就不一样了,那么这个算法就废弃之前的p包括里面的所有子节点,从新构建一个span节点和子节点;

>2. 很明显因为根节点不同就将所有的子节点从新构建,这很浪费,但是为了避免O(N*3)的时间复杂度,React这能选择这种比较简单、快捷的方法;

>3. 所以,作为开发者,我们一定要避免上面的浪费的情景出现

节点类型相同的情况

如果两个节点类型相同时,对于DOM元素,React会保留节点对应的DOM元素,只对其节点的属性和内容做对比,然后只修改更新的部分;

节点类型相同时,对于React组件类型,React做得是根据新节点的props去更新节点的组件实例,引发组件的更新过程;

在处理完根节点对比后,React的算法会对根节点的每一个子节点重复一样的操作

多个相同子组件的情况

如果最初组件状态为:

<ul>
  <TodoItem text = "First" />
  <TodoItem text = "Second" />

</ul>

更新后为:

<ul>
  <TodoItem text = "First" />
  <TodoItem text = "Second" />
  <TodoItem text = "Third" />
</ul>

那么React会创建一个新的TodoItem组件实例,而前两个则进行正常的更新过程但是,如果更新后为:

<ul>
  <TodoItem text = "Zero" />
  <TodoItem text = "First" />
  <TodoItem text = "Second" />

</ul>

(这将暴露一个问题)理想处理方式是,创建一个新的TodoItem组件实例放在第一位,后两个进入自然更新过程
但是要让react按照这种方式,就必须找两个子组件的不同之处,而现有计算两个序列差异的算法时间是O(N*2),显然则
不适合对性能要求很高的场景,所以React选择了一个看起来很傻的办法,即挨个比较每个子组件;

React首先认为把text为First的组件的text改为Zero,Second的改为First,最后创建一个text为Second的组件,这样便会破原有的两个组件完成一个更新过程,并创建一个text为Second的新组件

这显然是一个浪费,React也意到,并提供了方克服,不过需要开发人员提供一点帮助,这就是key

Key的使用

key属性可以明确的告诉React每个组件的唯一标识

如果最初组件状态为:

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

</ul>

更新后为:

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

因为有唯一标识key,React可以根据key值,知道现在的第二和第三个组件就是之前的第一和第二个,便用原来的props启动更新过程,这样shouldComponentUpdate就会发生作用,避免无谓的更新;

注意:因为作为组件的唯一标识,所以key必须唯一,且不可变

下面的代码是错误的例子:

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

使用数组下标作为key值,看起来唯一,但不稳定,因为随着todos数组值的不同,同样一个组件实例在不同的更新过程中数组的下标完全可能不同,把下标当做可以就会让React乱套,记住key不仅要唯一还要确保稳定不可变

需要注意:虽然key是一个prop,但是接受key的组件不能读取key的值,因为key和ref是React保留的两个特殊prop,并没有预期让组将直接访问。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

如何关闭Vue计算属性自带的缓存功能,具体步骤有哪些?

有關在Vue中使用Compass的具體方法?

利用vue2.0中swiper元件實作輪播(詳細教學)

以上是有關React組件效能優化詳細講解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn