首頁 > web前端 > js教程 > React元件效能優化方法解答

React元件效能優化方法解答

小云云
發布: 2018-03-03 09:00:42
原創
1258 人瀏覽過

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

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,并没有预期让组将直接访问。

相关推荐:

React组件生命周期详解

React组件Dragact 0.1.4详解

store优化React组件的方法详解

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

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板