React性能優(yōu)化

在以下場景中扬跋,父組件和子組件通常會重新渲染:
在同一組件或父組件中調(diào)用 setState 時。
從父級收到的“props”的值發(fā)生變化凌节。
調(diào)用組件中的 forceUpdate钦听。

下面是提升 React 應(yīng)用性能的 21 個技巧。
1.使用純組件
如果 React 組件為相同的狀態(tài)和 props 渲染相同的輸出倍奢,則可以將其視為純組件朴上。
對于像 this 的類組件來說,React 提供了 PureComponent 基類卒煞。擴(kuò)展 React.PureComponent 類的類組件被視為純組件痪宰。
它與普通組件是一樣的,只是 PureComponents 負(fù)責(zé) shouldComponentUpdate——它對狀態(tài)和 props 數(shù)據(jù)進(jìn)行淺層比較(shallow comparison)畔裕。
如果先前的狀態(tài)和 props 數(shù)據(jù)與下一個 props 或狀態(tài)相同衣撬,則組件不會重新渲染。

什么是淺層渲染柴钻?
在對比先前的 props 和狀態(tài)與下一個 props 和狀態(tài)時淮韭,淺層比較將檢查它們的基元是否有相同的值(例如:1 等于 1 或真等于真)垢粮,還會檢查更復(fù)雜的 JavaScript 值(如對象和數(shù)組)之間的引用是否相同贴届。

比較基元和對象引用的開銷比更新組件視圖要低。
因此蜡吧,查找狀態(tài)和 props 值的變化會比不必要的更新更快毫蚓。

setstate 在一秒的間隔之后被調(diào)用,這將重新觸發(fā)組件的視圖渲染昔善。由于初始 props 和新 props 的值相同元潘,因此組件(PureChildComponent)不會被重新渲染。

狀態(tài)的淺層比較表明 props 或狀態(tài)的數(shù)據(jù)沒有變化君仆,因此不需要渲染組件翩概,從而提升了性能牲距。

2.使用 React.memo 進(jìn)行組件記憶
React.memo 是一個高階組件。
它很像 PureComponent钥庇,但 PureComponent 屬于 Component 的類實現(xiàn)牍鞠,而“memo”則用于創(chuàng)建函數(shù)組件。
這里與純組件類似评姨,如果輸入 props 相同則跳過組件渲染难述,從而提升組件性能。
它會記憶上次某個輸入 prop 的執(zhí)行輸出并提升應(yīng)用性能吐句。即使在這些組件中比較也是淺層的胁后。
你還可以為這個組件傳遞自定義比較邏輯。
用戶可以用自定義邏輯深度對比(deep comparison)對象嗦枢。如果比較函數(shù)返回 false 則重新渲染組件攀芯,否則就不會重新渲染。

如果我們將對象引用作為 props 傳遞給 memo 組件文虏,則需要一些自定義登錄以進(jìn)行比較敲才。在這種情況下,我們可以將比較函數(shù)作為第二個參數(shù)傳遞給 React.memo 函數(shù)择葡。

假設(shè) props 值(user)是一個對象引用紧武,包含特定用戶的 name、age 和 designation敏储。

這種情況下需要進(jìn)行深入比較阻星。我們可以創(chuàng)建一個自定義函數(shù),查找前后兩個 props 值的 name已添、age 和 designation 的值妥箕,如果它們不相同則返回 false。

這樣更舞,即使我們將參考數(shù)據(jù)作為 memo 組件的輸入畦幢,組件也不會重新渲染。
function CustomisedComponen(props) {
return (
<div>
<b>User name: {props.user.name}</b>
<b>User age: {props.user.age}</b>
<b>User designation: {props.user.designation}</b>
</div>
)
}

function userComparator(previosProps, nextProps) {
if(previosProps.user.name == nextProps.user.name ||
previosProps.user.age == nextProps.user.age ||
previosProps.user.designation == nextProps.user.designation) {
return false
} else {
return true;
}
}

var memoComponent = React.memo(CustomisedComponent, userComparator);

上面的代碼提供了用于比較的自定義邏輯

3缆蝉、使用 shouldComponentUpdate 生命周期事件
這是在重新渲染組件之前觸發(fā)的其中一個生命周期事件宇葱。
可以利用此事件來決定何時需要重新渲染組件。如果組件 props 更改或調(diào)用 setState刊头,則此函數(shù)返回一個 Boolean 值黍瞧。

在這兩種情況下組件都會重新渲染。我們可以在這個生命周期事件中放置一個自定義邏輯原杂,以決定是否調(diào)用組件的 render 函數(shù)印颤。

這個函數(shù)將 nextState 和 nextProps 作為輸入,并可將其與當(dāng)前 props 和狀態(tài)做對比穿肄,以決定是否需要重新渲染年局。

shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false
}
這里即使組件中其他參數(shù)發(fā)生變化也不會影響應(yīng)用的視圖际看。
shouldComponentUpdate 將輸入?yún)?shù)作為狀態(tài)和 props 的新值。
我們可以比較 name 和 age 的當(dāng)前值和新值矢否。有任何一個發(fā)生變化就可以觸發(fā)重新渲染仿村。
從 shouldComponentUpdate 傳遞 true 就意味著可以重新渲染組件,反之亦然兴喂。所以正確使用 shouldComponentUpdate 就可以優(yōu)化應(yīng)用組件的性能蔼囊。

對比過初始狀態(tài)和 props 后我們就可以決定是否需要重新渲染組件。這樣可以減少重新渲染的需求來提升性能衣迷。

4畏鼓、懶加載組件
導(dǎo)入多個文件合并到一個文件中的過程叫打包,使應(yīng)用不必導(dǎo)入大量外部文件壶谒。
所有主要組件和外部依賴項都合并為一個文件云矫,通過網(wǎng)絡(luò)傳送出去以啟動并運(yùn)行 Web 應(yīng)用。

這樣可以節(jié)省大量網(wǎng)絡(luò)調(diào)用汗菜,但這個文件會變得很大让禀,消耗大量網(wǎng)絡(luò)帶寬。

應(yīng)用需要等待這個文件的加載和執(zhí)行陨界,所以傳輸延遲會帶來嚴(yán)重的影響巡揍。

為了解決這個問題,我們引入代碼拆分的概念菌瘪。

像 webpack 這樣的打包器支持就支持代碼拆分腮敌,它可以為應(yīng)用創(chuàng)建多個包,并在運(yùn)行時動態(tài)加載俏扩,減少初始包的大小糜工。

為此我們使用 Suspense 和 lazy。
if(this.props.name == "Mayank") {
ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
} else if(this.props.name == "Anshul") {
ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
}
return (
<div>
<h1>This is the Base User: {this.state.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<ComponentToLazyLoad />
</Suspense>
</div>
)
上面的代碼中有一個條件語句录淡,它查找 props 值捌木,并根據(jù)指定的條件加載主組件中的兩個組件。

我們可以按需懶惰加載這些拆分出來的組件嫉戚,增強(qiáng)應(yīng)用的整體性能刨裆。

假設(shè)有兩個組件 WelcomeComponent 或 GuestComponents,我們根據(jù)用戶是否登錄而渲染其中一個彼水。

我們可以根據(jù)具體的條件延遲組件加載崔拥,無需一開始就加載兩個組件极舔。
if(this.props.username !== "") {
const WelcomeComponent = lazy(() => import("./welcomeComponent"));
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<WelcomeComponent />
</Suspense>
</div>
)
} else {
const GuestComponent = lazy(() => import("./guestComponent"));
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<GuestComponent />
</Suspense>
</div>
)
}
在上面的代碼中我們沒有預(yù)加載 WelcomeCompoment 和 GuestComponents 這兩個組件凤覆,而是進(jìn)行一個條件檢查。

如果用戶名存在(或相反)拆魏,我們就根據(jù)指定的條件決定將某個組件作為單獨(dú)的包加載盯桦。

這個方法的好處
主包體積變小慈俯,消耗的網(wǎng)絡(luò)傳輸時間更少。

動態(tài)單獨(dú)加載的包比較小拥峦,可以迅速加載完成贴膘。

我們可以分析應(yīng)用來決定懶加載哪些組件,從而減少應(yīng)用的初始加載時間略号。

5刑峡、使用 React Fragments 避免額外標(biāo)記
使用 Fragments 減少了包含的額外標(biāo)記數(shù)量,這些標(biāo)記只是為了滿足在 React 組件中具有公共父級的要求玄柠。
用戶創(chuàng)建新組件時突梦,每個組件應(yīng)具有單個父標(biāo)簽。父級不能有兩個標(biāo)簽羽利,所以頂部要有一個公共標(biāo)簽宫患。所以我們經(jīng)常在組件頂部添加額外標(biāo)簽,例如:

在上面指定的組件中这弧,我們需要一個額外的標(biāo)簽為要渲染的組件提供公共父級娃闲。

除了充當(dāng)組件的父標(biāo)簽之外,這個額外的 div 沒有其他用途匾浪。

在頂層有多個標(biāo)簽會導(dǎo)致以下錯誤:

要解決此問題皇帮,我們可以將元素包含在片段(fragement)中。

片段不會向組件引入任何額外標(biāo)記蛋辈,但它仍然為兩個相鄰標(biāo)記提供父級玲献,因此滿足在組件頂級具有單個父級的條件。
<>
<h1>This is the Header Component</h1>
</>
上面的代碼沒有額外的標(biāo)記梯浪,因此節(jié)省了渲染器渲染額外元素的工作量捌年。

6、不要使用內(nèi)聯(lián)函數(shù)定義
如果我們使用內(nèi)聯(lián)函數(shù)挂洛,則每次調(diào)用“render”函數(shù)時都會創(chuàng)建一個新的函數(shù)實例礼预。
當(dāng) React 進(jìn)行虛擬 DOM diffing 時,它每次都會找到一個新的函數(shù)實例虏劲;因此在渲染階段它會會綁定新函數(shù)并將舊實例扔給垃圾回收托酸。

因此直接綁定內(nèi)聯(lián)函數(shù)就需要額外做垃圾回收和綁定到 DOM 的新函數(shù)的工作。
<input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value="Click For Inline Function" />

上面的函數(shù)創(chuàng)建了內(nèi)聯(lián)函數(shù)柒巫。每次調(diào)用 render 函數(shù)時都會創(chuàng)建一個函數(shù)的新實例励堡,render 函數(shù)會將該函數(shù)的新實例綁定到該按鈕。

此外最后一個函數(shù)實例會被垃圾回收堡掏,大大增加了 React 應(yīng)用的工作量应结。

所以不要用內(nèi)聯(lián)函數(shù),而是在組件內(nèi)部創(chuàng)建一個函數(shù),并將事件綁定到該函數(shù)本身鹅龄。這樣每次調(diào)用 render 時就不會創(chuàng)建單獨(dú)的函數(shù)實例了揩慕,參考組件如下。
setNewStateData = (event) => {
this.setState({
inputValue: e.target.value
})
}
<input type="button" onClick={this.setNewStateData} value="Click For Inline Function" />

7扮休、避免 componentWillMount() 中的異步請求
componentWillMount 是在渲染組件之前調(diào)用的迎卤。
這個函數(shù)用的不多,可用來配置組件的初始配置玷坠,但使用 constructor 方法自己也能做到蜗搔。

該方法無法訪問 DOM 元素,因為組件還沒掛載上來八堡。

一些開發(fā)人員認(rèn)為這個函數(shù)可以用來做異步數(shù)據(jù) API 調(diào)用碍扔,但其實這沒什么好處。

由于 API 調(diào)用是異步的秕重,因此組件在調(diào)用 render 函數(shù)之前不會等待 API 返回數(shù)據(jù)不同。于是在初始渲染中渲染組件時沒有任何數(shù)據(jù)。
componentWillMount() {
axios.get("someResourceUrl").then((data) => {
this.setState({
userData: data
});
});
}
在上面的代碼中溶耘,我們正在進(jìn)行異步調(diào)用以獲取數(shù)據(jù)二拐。由于數(shù)據(jù)調(diào)用是異步的,需要一段時間才能獲取到凳兵。

在檢索數(shù)據(jù)時 React 會觸發(fā)組件的 render 函數(shù)百新。因此第一個調(diào)用的渲染仍然不包含它所需的數(shù)據(jù)。

這樣一開始渲染組件沒有數(shù)據(jù)庐扫,然后檢索數(shù)據(jù)饭望,調(diào)用 setState,還得重新渲染組件形庭。在 componentWillMount 階段進(jìn)行 AJAX 調(diào)用沒有好處可言铅辞。

我們應(yīng)避免在此函數(shù)中發(fā)出 Async 請求。這些函數(shù)和調(diào)用可以延遲到 componentDidMount 生命周期事件里萨醒。

注意:React 16.3 不推薦使用 componentWillMount斟珊。如果你使用的是最新版本的 React,請避免使用這個生命周期事件富纸。

8囤踩、在 Constructor 的早期綁定函數(shù)
當(dāng)我們在 React 中創(chuàng)建函數(shù)時,我們需要使用 bind 關(guān)鍵字將函數(shù)綁定到當(dāng)前上下文晓褪。
綁定可以在構(gòu)造函數(shù)中完成堵漱,也可以在我們將函數(shù)綁定到 DOM 元素的位置上完成。

兩者之間似乎沒有太大差異涣仿,但性能表現(xiàn)是不一樣的勤庐。
handleButtonClick() {
alert("Button Clicked: " + this.state.name)
}

render() {
return (
<>
<input type="button" value="Click" onClick={this.handleButtonClick.bind(this)} />
</>
)
}
在上面的代碼中,我們在 render 函數(shù)的綁定期間將函數(shù)綁定到按鈕上。

上面代碼的問題在于埃元,每次調(diào)用 render 函數(shù)時都會創(chuàng)建并使用綁定到當(dāng)前上下文的新函數(shù)涝涤,但在每次渲染時使用已存在的函數(shù)效率更高媚狰。優(yōu)化方案如下:
constructor() {
this.state = {
name: "Mayank"
}
this.handleButtonClick = this.handleButtonClick.bind(this)
}

handleButtonClick() {
alert("Button Clicked: " + this.state.name)
}

render() {
return (
<>
<input type="button" value="Click" onClick={this.handleButtonClick} />
</>
)
}
最好在構(gòu)造函數(shù)調(diào)用期間使用綁定到當(dāng)前上下文的函數(shù)覆蓋 handleButtonClick 函數(shù)岛杀。

這將減少將函數(shù)綁定到當(dāng)前上下文的開銷,無需在每次渲染時重新創(chuàng)建函數(shù)崭孤,從而提高應(yīng)用的性能类嗤。

9、箭頭函數(shù)與構(gòu)造函數(shù)中的綁定
處理類時的標(biāo)準(zhǔn)做法就是使用箭頭函數(shù)辨宠。使用箭頭函數(shù)時會保留執(zhí)行的上下文遗锣。
我們調(diào)用它時不需要將函數(shù)綁定到上下文。
handleButtonClick = () => {
alert("Button Clicked: " + this.state.name)
}

render() {
return (
<>
<input type="button" value="Click" onClick={this.handleButtonClick} />
</>
)
}
箭頭函數(shù)好處多多嗤形,但也有缺點(diǎn)精偿。

當(dāng)我們添加箭頭函數(shù)時,該函數(shù)被添加為對象實例赋兵,而不是類的原型屬性笔咽。這意味著如果我們多次復(fù)用組件,那么在組件外創(chuàng)建的每個對象中都會有這些函數(shù)的多個實例霹期。

每個組件都會有這些函數(shù)的一份實例叶组,影響了可復(fù)用性。此外因為它是對象屬性而不是原型屬性历造,所以這些函數(shù)在繼承鏈中不可用甩十。

因此箭頭函數(shù)確實有其缺點(diǎn)。實現(xiàn)這些函數(shù)的最佳方法是在構(gòu)造函數(shù)中綁定函數(shù)吭产,如上所述侣监。

10、避免使用內(nèi)聯(lián)樣式屬性
使用內(nèi)聯(lián)樣式時瀏覽器需要花費(fèi)更多時間來處理腳本和渲染臣淤,因為它必須映射傳遞給實際 CSS 屬性的所有樣式規(guī)則达吞。
<b style={{"backgroundColor": "blue"}}>Welcome to Sample Page</b>

在上面創(chuàng)建的組件中,我們將內(nèi)聯(lián)樣式附加到組件荒典。添加的內(nèi)聯(lián)樣式是 JavaScript 對象而不是樣式標(biāo)記酪劫。

樣式 backgroundColor 需要轉(zhuǎn)換為等效的 CSS 樣式屬性,然后才應(yīng)用樣式寺董。這樣就需要額外的腳本處理和 JS 執(zhí)行工作覆糟。

更好的辦法是將 CSS 文件導(dǎo)入組件。

11遮咖、優(yōu)化 React 中的條件渲染
安裝和卸載 React 組件是昂貴的操作滩字。為了提升性能,我們需要減少安裝和卸載的操作。
組件 HeaderComponent 和 ContentComponent 將在位置 1 和位置 2 卸載并重新安裝麦箍。其實這是用不著的漓藕,因為這些組件沒有更改,這是一項昂貴的操作挟裂。優(yōu)化方案如下:
{ this.state.name == "Mayank" && <AdminHeaderComponent></AdminHeaderComponent> }
<HeaderComponent></HeaderComponent>
<ContentComponent></ContentComponent>
在上面的代碼中享钞,當(dāng) name 不是 Mayank 時,React 在位置 1 處放置 null诀蓉。

開始 DOM diffing 時栗竖,位置 1 的元素從 AdminHeaderComponent 變?yōu)?null,但位置 2 和位置 3 的組件保持不變渠啤。

由于元素沒變践剂,因此組件不會卸載凄杯,減少了不必要的操作。

12、不要在 render 方法中導(dǎo)出數(shù)據(jù)
Render 方法是 React 開發(fā)人員最熟悉的生命周期事件避乏。
和其他生命周期事件不一樣的是芥驳,我們的核心原則是將 render() 函數(shù)作為純函數(shù)郑藏。

純函數(shù)對 render 方法意味著什么旬痹?
純函數(shù)意味著我們應(yīng)該確保 setState 和查詢原生 DOM 元素等任何可以修改應(yīng)用狀態(tài)的東西不會被調(diào)用。

該函數(shù)永遠(yuǎn)不該更新應(yīng)用的狀態(tài)部脚。

更新組件狀態(tài)的問題在于想邦,當(dāng)狀態(tài)更新時會觸發(fā)另一個 render 循環(huán),后者在內(nèi)部會再觸發(fā)一個 render 循環(huán)委刘,以此類推丧没。
this.setState({
name: this.state.name + "_"
});

return (
  <div>
    <b>User Name: {this.state.name}</b>
  </div>

);
在上面的代碼中,每次調(diào)用 render 函數(shù)時都會更新狀態(tài)锡移。狀態(tài)更新后組件將立即重新渲染呕童。因此更新狀態(tài)會導(dǎo)致 render 函數(shù)的遞歸調(diào)用。

render 函數(shù)應(yīng)保持純凈淆珊,以確保組件以一致的方式運(yùn)行和渲染夺饲。

13、為組件創(chuàng)建錯誤邊界
組件渲染錯誤是很常見的情況施符。
在這種情況下往声,組件錯誤不應(yīng)該破壞整個應(yīng)用。創(chuàng)建錯誤邊界可避免應(yīng)用在特定組件發(fā)生錯誤時中斷戳吝。

錯誤邊界是一個 React 組件浩销,可以捕獲子組件中的 JavaScript 錯誤。我們可以包含錯誤听哭、記錄錯誤消息慢洋,并為 UI 組件故障提供回退機(jī)制塘雳。

錯誤邊界是基于高階組件的概念。

詳細(xì)信息參閱: https://levelup.gitconnected.com/introduction-to-reacts-higher-order-components-hocs-c42182fb634

錯誤邊界涉及一個高階組件普筹,包含以下方法:static getDerivedStateFromError() 和 componentDidCatch()败明。

static 函數(shù)用于指定回退機(jī)制,并從收到的錯誤中獲取組件的新狀態(tài)太防。

componentDidCatch 函數(shù)用來將錯誤信息記錄到應(yīng)用中妻顶。

下面是代碼示例:
import React from 'react';

export class ErrorBoundaries extends React.Component {
constructor(props) {
super(props);
this.state = {
hasErrors: false
}
}

componentDidCatch(error, info) {
    console.dir("Component Did Catch Error");
}

static getDerivedStateFromError(error) {
    console.dir("Get Derived State From Error");
    return {
        hasErrors: true
    }
}

render() {

    if(this.state.hasErrors === true) {
        return <div>This is a Error</div>
    }

    return <div><ShowData name="Mayank" /></div>
}

}

export class ShowData extends React.Component {

constructor() {
    super();
    this.state = {
        name: "Mayank"
    }
}

changeData = () => {
   this.setState({
       name: "Anshul"
   })
}
render() {

    if(this.state.name === "Anshul") {
        throw new Error("Sample Error")
    }

    return (
        <div>
            <b>This is the Child Component {this.state.name}</b>
            <input type="button" onClick={this.changeData} value="Click To Throw Error" />
        </div>
    )
}

}

當(dāng) name 更新為 Anshul 時,上面的代碼會拋出錯誤杏头。

組件 ShowData 是 ErrorBoundaries 組件內(nèi)的嵌入盈包。

因此沸呐,如果錯誤是從 ShowData 函數(shù)內(nèi)拋出的醇王,則它會被父組件捕獲,我們使用 static getDerivedStateFromError 函數(shù)和 componentDidCatch 生命周期事件中的日志數(shù)據(jù)部署回退 UI崭添。

14寓娩、組件的不可變數(shù)據(jù)結(jié)構(gòu)
React 的靈魂是函數(shù)式編程。如果我們希望組件能一致工作呼渣,則 React 組件中的狀態(tài)和 props 數(shù)據(jù)應(yīng)該是不可變的棘伴。
對象的突變可能導(dǎo)致輸出不一致。
shouldComponentUpdate(nextProps, nextState) {
if(nextState.userInfo != this.state.userInfo) {
return true;
}
}

render() {
return (
<>
<b>User Name: {this.state.userName}
</>
)
}
如上所示屁置。在 shouldComponentUpdate 函數(shù)中我們指定焊夸,如果 userInfo 的初始值與 userInfo 的新值不同,則應(yīng)該重新渲染該組件蓝角;反之不應(yīng)重新渲染組件阱穗。

15、使用唯一鍵迭代
當(dāng)我們需要渲染項目列表時應(yīng)該為項目添加一個鍵使鹅。
鍵可以用來識別已更改揪阶、添加或刪除的項目。鍵為元素提供了穩(wěn)定的標(biāo)識患朱。一個鍵應(yīng)該對應(yīng)列表中的唯一一個元素鲁僚。

如果開發(fā)人員沒有為元素提供鍵,則它將 index 作為默認(rèn)鍵裁厅。在下面的代碼中我們默認(rèn)不添加任何鍵冰沙,因此 index 將用作列表的默認(rèn)鍵。

使用 index 作為鍵就不會出現(xiàn)標(biāo)識不唯一的問題了执虹,因為 index 只會標(biāo)識所渲染的組件拓挥。

我們可以在以下場景中使用 index 作為鍵:

列表項是靜態(tài)的,項目不隨時間變化声畏。

Items 沒有唯一 ID撞叽。

List 永遠(yuǎn)不會重新排序或過濾姻成。

不會從頂部或中間添加或刪除項目。
constructor() {
super();
this.state = {
inputName: "",
arrayData: ["Mayank", "Meha", "Anshul", "Arjun"]
}
}

updateUserName(event) {
    this.setState({
        inputName: event.target.value
    })
}

addUserData() {
    this.setState({
        arrayData: [this.state.inputName, ...this.state.arrayData]
    })
}

render() {
    var dataList = this.state.arrayData.map((data, index) => {
        return <div>{data}</div>;
    })
    return (
        <div>
            <input type="text" value={this.state.inputName} placeholder="Enter Unique Name" onChange={this.updateUserName.bind(this)} />
            <input type="button" onClick={this.addUserData.bind(this)} value="Click To Add" /><br></br><br></br>
            <b>List of Users: </b><br></br><br></br>
            {dataList}<br></br>
        </div>
    )
}

在列表中添加項目
使用 index 作為鍵會加大錯誤率并降低應(yīng)用的性能愿棋。

每當(dāng)新元素添加到列表時科展,默認(rèn)情況下 React 會同時遍歷新創(chuàng)建的列表和舊列表,并在需要時進(jìn)行突變糠雨。

在列表頂部添加一個新元素(包含 index 作為鍵)時才睹,全部已有組件的索引都會更新。

索引更新后甘邀,之前鍵值為 1 的元素現(xiàn)在的鍵值變成了 2琅攘。更新所有組件會拖累性能。

上面的代碼允許用戶在列表頂部添加新項目松邪。但在頂部插入元素后果最嚴(yán)重坞琴。因為頂部元素一變,后面所有的元素都得跟著改鍵值逗抑,從而導(dǎo)致性能下降剧辐。

因此,我們應(yīng)該確保鍵值和元素一一對應(yīng)不會變化邮府。

總結(jié)一下:

Key 不僅影響性能荧关,更重要的作用是標(biāo)識。隨機(jī)分配和更改的值不算是標(biāo)識褂傀。

我們得知道數(shù)據(jù)的建模方式才能提供合適的鍵值忍啤。如果你沒有 ID,我建議使用某種哈希函數(shù)生成 ID仙辟。

我們在使用數(shù)組時已經(jīng)有了內(nèi)部鍵同波,但它們是數(shù)組中的索引。插入新元素時這些鍵是錯誤的欺嗤。

16参萄、事件節(jié)流和防抖
節(jié)流(throttling)和防抖(debouncing)可用來限制在指定時間內(nèi)調(diào)用的事件處理程序的數(shù)量。
事件處理程序是響應(yīng)不同事件(如鼠標(biāo)單擊和頁面滾動)而調(diào)用的函數(shù)煎饼。事件觸發(fā)事件處理程序的速率是不一樣的讹挎。

節(jié)流的概念
節(jié)流意味著延遲函數(shù)執(zhí)行。

這些函數(shù)不會立即執(zhí)行吆玖,在觸發(fā)事件之前會加上幾毫秒延遲筒溃。

比如在頁面滾動時,我們不會過于頻繁地觸發(fā)滾動事件沾乘,而是將事件延遲一段時間以便將多個事件堆疊在一起怜奖。

它確保函數(shù)在特定時間段內(nèi)至少調(diào)用一次。如果函數(shù)最近運(yùn)行過了翅阵,它將阻止函數(shù)運(yùn)行歪玲,確保函數(shù)以固定間隔定期運(yùn)行迁央。

當(dāng)我們處理無限滾動并且當(dāng)用戶接近頁面底部必須獲取數(shù)據(jù)時,我們可以使用節(jié)流滥崩。

否則滾動到頁面底部將觸發(fā)多個事件岖圈,并且觸發(fā)對網(wǎng)絡(luò)的多次調(diào)用,從而導(dǎo)致性能問題钙皮。

防抖的概念
防抖是指在調(diào)用停止一段時間之前忽略事件處理程序調(diào)用蜂科。

假設(shè)我們有一個事件,有一秒鐘的 debounce 時間短条。一旦用戶停止觸發(fā)事件导匣,該事件的事件處理程序?qū)⒃谝幻腌姾笥|發(fā)。

典型的例子是用戶在自動填充搜索框中鍵入數(shù)據(jù)茸时。

一旦用戶停止鍵入贡定,就會進(jìn)行 AJAX 查詢以從 API 獲取數(shù)據(jù)。每次鍵入都進(jìn)行 AJAX 調(diào)用就需要多次查詢數(shù)據(jù)庫屹蚊。

因此厕氨,我們對該事件做 debounce进每,直到用戶不再輸入數(shù)據(jù)為止汹粤,從而減少網(wǎng)絡(luò)調(diào)用并提升性能。

17田晚、使用 CDN
谷歌嘱兼、亞馬遜和微軟等公司提供了許多內(nèi)容分發(fā)網(wǎng)絡(luò)。
這些 CDN 是可在你的應(yīng)用中使用的外部資源贤徒。我們甚至可以創(chuàng)建私有 CDN 并托管我們的文件和資源芹壕。

使用 CDN 有以下好處:

不同的域名。瀏覽器限制了單個域名的并發(fā)連接數(shù)量接奈,具體取決于瀏覽器設(shè)置踢涌。假設(shè)允許的并發(fā)連接數(shù)為 10。如果要從單個域名中檢索 11 個資源序宦,那么同時完成的只有 10 個睁壁,還有 1 個需要再等一會兒。CDN 托管在不同的域名 / 服務(wù)器上互捌。因此資源文件可以分布在不同的域名中潘明,提升了并發(fā)能力。

文件可能已被緩存秕噪。有很多網(wǎng)站使用這些 CDN钳降,因此你嘗試訪問的資源很可能已在瀏覽器中緩存好了。這時應(yīng)用將訪問文件的已緩存版本腌巾,從而減少腳本和文件執(zhí)行的網(wǎng)絡(luò)調(diào)用和延遲遂填,提升應(yīng)用性能铲觉。

高容量基礎(chǔ)設(shè)施。這些 CDN 由大公司托管吓坚,因此可用的基礎(chǔ)設(shè)施非常龐大备燃。他們的數(shù)據(jù)中心遍布全球。向 CDN 發(fā)出請求時凌唬,它們將通過最近的數(shù)據(jù)中心提供服務(wù)并齐,從而減少延遲。這些公司會對服務(wù)器做負(fù)載平衡客税,以確保請求到達(dá)最近的服務(wù)器并減少網(wǎng)絡(luò)延遲况褪,提升應(yīng)用性能。

如果擔(dān)心安全性更耻,可以使用私有 CDN测垛。

18、用 CSS 動畫代替 JavaScript 動畫
在 HTML 5 和 CSS 3 出現(xiàn)之前秧均,動畫曾經(jīng)是 JavaScript 的專屬食侮,但隨著 HTML 5 和 CSS 3 的引入情況開始變化。現(xiàn)在動畫甚至可以由 CSS 3 來處理了目胡。
我們可以制定一些規(guī)則:

如果 CSS 可以實現(xiàn)某些 JS 功能锯七,那就用 CSS。

如果 HTML 可以實現(xiàn)某些 JS 功能誉己,那就用 HTML眉尸。

理由如下:

破損的 CSS 規(guī)則和樣式不會導(dǎo)致網(wǎng)頁損壞,而 JavaScript 則不然巨双。

解析 CSS 是非常便宜的噪猾,因為它是聲明性的。我們可以為樣式并行創(chuàng)建內(nèi)存中的表達(dá)筑累,可以推遲樣式屬性的計算袱蜡,直到元素繪制完成。

為動畫加載 JavaScript 庫的成本相對較高慢宗,消耗更多網(wǎng)絡(luò)帶寬和計算時間坪蚁。

雖然 JavaScript 可以提供比 CSS 更多的優(yōu)化,但優(yōu)化過的 JavaScript 代碼也可能卡住 UI 并導(dǎo)致 Web 瀏覽器崩潰婆廊。

19迅细、在 Web 服務(wù)器上啟用 gzip 壓縮
壓縮是節(jié)省網(wǎng)絡(luò)帶寬和加速應(yīng)用的最簡單方法。
我們可以把網(wǎng)絡(luò)資源壓縮到更小的尺寸淘邻。Gzip 是一種能夠快速壓縮和解壓縮文件的數(shù)據(jù)壓縮算法茵典。

它可以壓縮幾乎所有類型的文件,例如圖像宾舅、文本统阿、JavaScript 文件彩倚、樣式文件等。Gzip 減少了網(wǎng)頁需要傳輸?shù)娇蛻舳说臄?shù)據(jù)量扶平。

當(dāng) Web 服務(wù)器收到請求時帆离,它會提取文件數(shù)據(jù)并查找 Accept-Encoding 標(biāo)頭以確定如何壓縮應(yīng)用。

如果服務(wù)器支持 gzip 壓縮结澄,資源會被壓縮后通過網(wǎng)絡(luò)發(fā)送哥谷。每份資源的壓縮副本(添加了 Content-Encoding 標(biāo)頭)指定使用 gzip 解壓。

然后麻献,瀏覽器將內(nèi)容解壓縮原始版本在渲染給用戶们妥。

只是 gzip 壓縮需要付出成本,因為壓縮和解壓縮文件屬于 CPU 密集型任務(wù)勉吻。但我們還是建議對網(wǎng)頁使用 gzip 壓縮监婶。

20、使用 Web Workers 處理 CPU 密集任務(wù)
JavaScript 是一個單線程應(yīng)用齿桃,但在渲染網(wǎng)頁時需要執(zhí)行多個任務(wù):
處理 UI 交互惑惶、處理響應(yīng)數(shù)據(jù)、操縱 DOM 元素短纵、啟用動畫等带污。所有這些任務(wù)都由單個線程處理。

可以使用 worker 來分擔(dān)主線程的負(fù)載踩娘。

Worker 線程在后臺運(yùn)行刮刑,可以在不中斷主線程的情況下執(zhí)行多個腳本和 JavaScript 任務(wù)。

每當(dāng)需要執(zhí)行長時間的 CPU 密集任務(wù)時养渴,可以使用 worker 在單獨(dú)的線程上執(zhí)行這些邏輯塊。

它們在隔離環(huán)境中執(zhí)行泛烙,并且使用進(jìn)程間線程通信與主線程交互理卑。主線程就可以騰出手來處理渲染和 DOM 操作任務(wù)。

詳細(xì)信息參閱: https://medium.com/prolanceer/optimizing-react-app-performance-using-web-workers-79266afd4a7

21蔽氨、React 組件的服務(wù)端渲染
服務(wù)端渲染可以減少初始頁面加載延遲藐唠。
我們可以讓網(wǎng)頁從服務(wù)端加載初始頁面,而不是在客戶端上渲染鹉究。這樣對 SEO 非常有利宇立。

服務(wù)端渲染是指第一個組件顯示的內(nèi)容是從服務(wù)器本身發(fā)送的,而不是在瀏覽器級別操作自赔。之后的頁面直接從客戶端加載妈嘹。

這樣我們就能把初始內(nèi)容放在服務(wù)端渲染,客戶端只按需加載部分頁面绍妨。

其好處包括:

性能:初始頁面內(nèi)容和數(shù)據(jù)是從服務(wù)器本身加載的润脸,因此我們不需要添加加載器和下拉列表柬脸,而是等待初始頁面加載完畢后再加載初始組件。

SEO 優(yōu)化:爬蟲在應(yīng)用初始加載時查找頁面內(nèi)容毙驯。在客戶端渲染時初始 Web 頁面不包含所需的組件倒堕,這些組件需要等 React 腳本等文件加載完畢后才渲染出來。

服務(wù)端渲染還可以使用第三方庫爆价,如 Next.js垦巴。詳細(xì)信息參閱: https://nextjs.org/

這里有服務(wù)端渲染的示例項目: https://github.com/Mayankgupta688/reactServerRendering。只需從項目存儲庫執(zhí)行以下步驟即可運(yùn)行應(yīng)用:

npm install

npm start

這個應(yīng)用中“pages”文件夾里的文件是可以用服務(wù)端渲染加載的初始 URL铭段。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魂那,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子稠项,更是在濱河造成了極大的恐慌涯雅,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件展运,死亡現(xiàn)場離奇詭異活逆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拗胜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門蔗候,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人埂软,你說我怎么就攤上這事锈遥。” “怎么了勘畔?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵所灸,是天一觀的道長。 經(jīng)常有香客問我炫七,道長爬立,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任万哪,我火速辦了婚禮侠驯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奕巍。我一直安慰自己吟策,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布的止。 她就那樣靜靜地躺著檩坚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上效床,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天睹酌,我揣著相機(jī)與錄音,去河邊找鬼剩檀。 笑死憋沿,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沪猴。 我是一名探鬼主播辐啄,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼运嗜!你這毒婦竟也來了壶辜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤担租,失蹤者是張志新(化名)和其女友劉穎砸民,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奋救,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岭参,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了尝艘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片演侯。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖背亥,靈堂內(nèi)的尸體忽然破棺而出秒际,到底是詐尸還是另有隱情,我是刑警寧澤狡汉,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布娄徊,位于F島的核電站,受9級特大地震影響轴猎,放射性物質(zhì)發(fā)生泄漏嵌莉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一捻脖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧中鼠,春花似錦可婶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春具温,著一層夾襖步出監(jiān)牢的瞬間蚕涤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工铣猩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留揖铜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓达皿,卻偏偏與公主長得像天吓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子峦椰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • React 為高性能應(yīng)用設(shè)計提供了許多優(yōu)化方案龄寞,本文列舉了其中的一些最佳實踐。 在以下場景中汤功,父組件和子組件通常會...
    Maco_wang閱讀 1,101評論 0 7
  • react 性能優(yōu)化 React 組件性能優(yōu)化的核心就是減少渲染真實DOM節(jié)點(diǎn)的頻率物邑,減少Virtual DOM ...
    開水泡飯閱讀 588評論 0 0
  • 這篇文章主要介紹了淺談react性能優(yōu)化的方法,小編覺得挺不錯的滔金,現(xiàn)在分享給大家色解,也給大家做個參考。一起跟隨小編過...
    880d91446f17閱讀 3,548評論 0 3
  • 今天給朋友們詳細(xì)介紹react如何進(jìn)行性能優(yōu)化鹦蠕。 首先要了解網(wǎng)頁性能不好的罪魁禍?zhǔn)?瀏覽器的回流【重排版】和重繪(...
    wenzi8705_GG閱讀 845評論 0 1
  • 哪些場景下冒签,父組件和子組件會重新渲染? 1.在同一組件或父組件中調(diào)用 setState 時钟病。 2.從父級收到的“p...
    CodeBetter閱讀 524評論 1 0