web前端培訓React效能優化總結

IT小尚發表於2022-05-09

前言

目的

目前在工作中,大量的專案都是使用 react 來進行開展的,瞭解掌握下 React 的效能優化對專案的體驗和可維護性都有很大的好處,下面介紹下在 React 中可以運用的一些效能優化方式;

效能優化思路

對於類式元件和函式式元件來看,都可以從以下幾個方面去思考如何能夠進行效能優化

  • 減少重新 Render 的次數

  • 減少渲染的節點

  • 降低渲染計算量

  • 合理設計元件

減少重新 Render 的次數

在 React 裡時間耗時最多的一個地方是 Reconciliation(reconciliation 的最終目標是以最有效的方式,根據新的狀態來更新 UI,我們可以簡單地理解為 diff),如果不執行 Render,也就不需要 Reconciliation,所以可以看出減少 Render 在效能優化過程中的重要程度了。

PureComponent

React.PureComponent 與 React.Component 很相似。兩者的區別在於 React.Component 並未實現 shouldComponentUpdate(),而 React.PureComponent 中以淺層對比 Prop 和 State 的方式來實現了該函式_

需要注意的是在使用 PureComponent 的元件中, 【關注尚矽谷,輕鬆學IT】在 Props 或者 State 的屬性值是物件的情況下,並不能阻止不必要的渲染,是因為自動載入的 shouldComponentUpdate 裡面做的只是淺比較,所以想要用 PureComponent 的特性,應該遵守原則:

  • 確保資料型別是值型別

  • 如果是引用型別,不應當有深層次的資料變化(解構)

ShouldComponentUpdate

可以利用此事件來決定何時需要重新渲染元件。如果元件 Props 更改或呼叫 setState,則此函式返回一個 Boolean 值,為 true 則會重新渲染元件,反之則不會重新渲染元件。

在這兩種情況下元件都會重新渲染。我們可以在這個生命週期事件中放置一個自定義邏輯,以決定是否呼叫元件的 Render 函式。

下面舉一個小的例子來輔助理解下:
比如要在你的應用中展示學生的詳細資料,每個學生都包含有多個屬性,如姓名、年齡、愛好、身高、體重、家庭住址、父母姓名等;在這個元件場景中,只需要展示學生的姓名、年齡、住址,其他的資訊不需要在這裡展示,所以在理想情況下,除去姓名、年齡、住址以外的資訊變化元件是不需要重新渲染的;

示例程式碼如下:

import React from"react";

exportdefaultclassShouldComponentUpdateUsageextendsReact.Component{

constructor(props) {

super(props);

this.state = {

name: "小明",

age: 12,

address: "xxxxxx",

height: 165,

weight: 40 }

}

componentDidMount() {

setTimeout(() => {

this.setState({

height: 168,

weight: 45 });

}, 5000)

}

shouldComponentUpdate(nextProps, nextState) {

if(nextState.name !== this.state.name || nextState.age !== this.state.age || nextState.address !== this.state.address) {

returntrue;

}

returnfalse;

}

render() {

const { name, age, address } = this.state;

return (

<div><p>Student name: {name} </p><p>Student age:{age} </p><p>Student address:{address} </p></div> )

}

}

按照 React 團隊的說法,shouldComponentUpdate 是保證效能的緊急出口,既然是緊急出口,那就意味著我們輕易用不到它。但既然有這樣一個緊急出口,那說明有時候它還是很有必要的。所以我們要搞清楚到底什麼時候才需要使用這個緊急出口。

使用原則

當你覺得,被改變的 State 或者 Props,不需要更新檢視時,你就應該思考要不要使用它。

需要注意的一個地方是:改變之後,又不需要更新檢視的狀態,也不應該放在 State 中。

shouldComponentUpdate 的使用,也是有代價的。如果處理得不好,甚至比多 Render 一次更消耗效能,另外也會使元件的複雜度增大,一般情況下使用PureComponent即可;

React.memo

如果你的元件在相同 Props 的情況下渲染相同的結果,那麼你可以通過將其包裝在 React.memo 中呼叫,以此通過記憶元件渲染結果的方式來提高元件的效能表現。 【關注尚矽谷,輕鬆學IT】這意味著在這種情況下,React 將跳過渲染元件的操作並直接複用最近一次渲染的結果。

React.memo 僅檢查 Props 變更。如果函式元件被 React.memo 包裹,且其實現中擁有 useState,useReducer 或 useContext 的 Hook,當 State 或 Context 發生變化時,它仍會重新渲染。

預設情況下其只會對複雜物件做淺層對比,如果你想要控制對比過程,那麼請將自定義的比較函式通過第二個引數傳入來實現。

functionMyComponent(props) {

/* 使用 props 渲染 */}

functionareEqual(prevProps, nextProps) {

/*

如果把 nextProps 傳入 render 方法的返回結果與

將 prevProps 傳入 render 方法的返回結果一致則返回 true,

否則返回 false

*/}

exportdefault React.memo(MyComponent, areEqual);

注意:與 Class 元件中 shouldComponentUpdate() 方法不同的是,如果 Props 相等,areEqual 會返回 true;如果 Props 不相等,則返回 false。這與 shouldComponentUpdate 方法的返回值相反。

合理使用 Context

Context 提供了一個無需為每層元件手動新增 Props,就能在元件樹間進行資料傳遞的方法。正是因為其這個特點,它是可以穿透 React.memo 或者 shouldComponentUpdate 的比對的,也就是說,一旦 Context 的 Value 變動,所有依賴該 Context 的元件會全部 forceUpdate.這個和 Mobx 和 vue 的響應式系統不同,Context api 並不能細粒度地檢測哪些元件依賴哪些狀態_

原則

  • Context 中只定義被大多陣列件所共用的屬性,例如當前使用者的資訊、主題或者選擇的語言。

避免使用匿名函式

首先來看下下面這段程式碼

const MenuContainer = ({ list }) => (

<Menu> {list.map((i) => (

<MenuItem key={i.id}=> handleClick(i.id)} value={i.value} />

))}

</Menu>);

上面這個寫法看起來是比較簡潔,但是有一個潛在問題是匿名函式在每次渲染時都會有不同的引用,這樣就會導致 Menu 元件會出現重複渲染的問題;可以使用 useCallback 來進行優化:

const MenuContainer = ({ list }) => {

const handleClick = useCallback(

(id) => () => {

// ... },

[],

);

return (

<Menu> {list.map((i) => (

<MenuItem key={i.id}id={i.id}/> ))}

</Menu> );

};

減少渲染的節點元件懶載入

元件懶載入可以讓 React 應用在真正需要展示這個元件的時候再去展示,可以比較有效的減少渲染的節點數提高頁面的載入速度

React 官方在 16.6 版本後引入了新的特性:React.lazy 和 React.Suspense,這兩個元件的配合使用可以比較方便進行元件懶載入的實現;

React.lazy

該方法主要的作用就是可以定義一個動態載入的元件,這可以直接縮減打包後 bundle 的體積,並且可以延遲載入在初次渲染時不需要渲染的元件,程式碼示例如下:

使用之前

import SomeComponent from'./SomeComponent';

使用之後

const SomeComponent = React.lazy(() => import('./SomeComponent'));

使用 React.lazy 的動態引入特性需要 JS 環境支援 Promise。在 IE11 及以下版本的瀏覽器中需要通過引入 Polyfill 來使用該特性。

React.Suspense

該元件目前主要的作用就是配合渲染 lazy 元件,這樣就可以在等待載入 lazy元件時展示 loading 元素,不至於直接空白,提升使用者體驗;

Suspense 元件中的 fallback 屬性接受任何在元件載入過程中你想展示的 React 元素。

你可以將 Suspense 元件置於懶載入元件之上的任何位置,你甚至可以用一個 Suspense 元件包裹多個懶載入元件。

程式碼示例如下:

import React, { Suspense } from'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

functionMyComponent() {

return (

<div><Suspense fallback={<div>Loading...</div>}>

<section><OtherComponent /><AnotherComponent /></section></Suspense></div> );

}

有一點要特別注意的是:React.lazy 和 Suspense 技術還不支援服務端渲染。如果你想要在使用服務端渲染的應用中使用,推薦使用 Loadable Components 這個庫,可以結合這個文件服務端渲染打包指南來進行檢視。

另外在業內也有一些比較成熟的 React 元件懶載入開源庫:react-loadable 和react-lazyload,感興趣的可以結合看下;

虛擬列表

虛擬列表是一種根據滾動容器元素的可視區域來渲染長列表資料中某一個部分資料的技術,在開發一些專案中,會遇到一些不是直接分頁來載入列表資料的場景,在這種情況下可以考慮結合虛擬列表來進行優化,可以達到根據容器元素的高度以及列表項元素的高度來顯示長列表資料中的某一個部分,而不是去完整地渲染長列表,以提高無限滾動的效能。

可以關注下放兩個比較常用的類庫來進行深入瞭解

  • react-virtualized

  • react-window

降低渲染計算量

useMemo

先來看下 useMemo 的基本使用方法:

functioncomputeExpensiveValue(a, b) {

// 計算量很大的一些邏輯return xxx

}

const memoizedValue = useMemo(computeExpensiveValue, [a, b]);

useMemo 的第一個引數就是一個函式,這個函式返回的值會被快取起來,同時這個值會作為 useMemo 的返回值,第二個引數是一個陣列依賴,如果陣列裡面的值有變化,那麼就會重新去執行第一個引數裡面的函式,並將函式返回的值快取起來並作為 useMemo 的返回值 。

注意

  • 如果沒有提供依賴項陣列,useMemo 在每次渲染時都會計算新的值;

  • 計算量如果很小的計算函式,也可以選擇不使用 useMemo,因為這點優化並不會作為效能瓶頸的要點,反而可能使用錯誤還會引起一些效能問題。

遍歷展示檢視時使用 key

key 幫助 React 識別哪些元素改變了,比如被新增或刪除。因此你應當給陣列中的每一個元素賦予一個確定的標識。

const numbers = [1, 2, 3, 4, 5];

const listItems = numbers.map((number) => <li key={number.toString()}> {number}

</li>);

使用 key 注意事項:

  • 最好是這個元素在列表中擁有的一個獨一無二的字串。通常,我們使用資料中的 id 來作為元素的 key,當元素沒有確定 id 的時候,萬不得已你可以使用元素索引 index 作為 key

  • 元素的 key 只有放在就近的陣列上下文中才有意義。例如,如果你提取出一個 ListItem 元件,你應該把 key 保留在陣列中的這個 元素上,而不是放在 ListItem 元件中的元素上。

合理設計元件

簡化 Props

如果一個元件的 Props 比較複雜的話,會影響 shallowCompare 的效率,也會使這個元件變得難以維護,另外也與“單一職責”的原則不符合,可以考慮進行拆解。

簡化 State

在設計元件的 State 時,可以按照這個原則來:需要元件響應它的變動或者需要渲染到檢視中的資料,才放到 State 中;這樣可以避免不必要的資料變動導致元件重新渲染。

減少元件巢狀

一般不必要的節點巢狀都是濫用高階元件/ RenderProps 導致的。所以還是那句話‘只有在必要時才使用 xxx’。有很多種方式來代替高階元件/ RenderProps,例如優先使用 Props、React Hooks。

文章轉載來源於web前端開發


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010293/viewspace-2893020/,如需轉載,請註明出處,否則將追究法律責任。

相關文章