React的性能優(yōu)化技巧

哪些場(chǎng)景下,父組件和子組件會(huì)重新渲染?

1.在同一組件或父組件中調(diào)用 setState 時(shí)。

2.從父級(jí)收到的“props”的值發(fā)生變化。

3.調(diào)用組件中的 forceUpdate。

1. 使用純組件

如果 React 組件為相同的狀態(tài)和 props 渲染相同的輸出,則可以將其視為純組件。

對(duì)于像 this 的類(lèi)組件來(lái)說(shuō),React 提供了 PureComponent 基類(lèi)。擴(kuò)展 React.PureComponent 類(lèi)的類(lèi)組件被視為純組件。

它與普通組件是一樣的,只是 PureComponents 負(fù)責(zé) shouldComponentUpdate——它對(duì)狀態(tài)和 props 數(shù)據(jù)進(jìn)行淺層比較(shallow comparison)。

如果先前的狀態(tài)和 props 數(shù)據(jù)與下一個(gè) props 或狀態(tài)相同,則組件不會(huì)重新渲染。


import React from 'react';

export default class ApplicationComponent extends React.Component {
constructor() {
      super();

    this.state = {

        name: "Mayank"
    }

}

updateState = () => {

    setInterval(() => {

        this.setState({

        name: "Mayank"

        })

    }, 1000)

}

componentDidMount() {

    this.updateState();

}

render() {
    console.log("Render Called Again")
    return (
        <div>
            <RegularChildComponent name={this.state.name} />
            <PureChildComponent name={this.state.name} />
        </div>
    )

}

}
class RegularChildComponent extends React.Component {
    render() {
        console.log("Regular Component Rendered..");
        return <div>{this.props.name}</div>;
    }
}

class PureChildComponent extends React.PureComponent {

    // Pure Components are the components that do not re-render if the State data or props data is still the same

    render() {
        console.log("Pure Component Rendered..")
        return <div>{this.props.name}</div>;
    }
}

2. 使用 React.memo 進(jìn)行組件記憶

React.memo 是一個(gè)高階組件。與上面說(shuō)的PureComponent很像,只是他是用在函數(shù)式組件上的,它允許你自定義比較邏輯,用戶(hù)可以用自定義邏輯深度對(duì)比(deep comparison)對(duì)象。如果比較函數(shù)返回 false 則重新渲染組件,否則就不會(huì)重新渲染。如下。


// The following function takes "user" Object as input parameter in props

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生命周期事件

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


import React from "react";

export default class ShouldComponentUpdateUsage extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      name: "Mayank";
      age: 30,
      designation: "Architect";
    }
  }

  componentDidMount() {
    setTimeout(() => {
      this.setState({
        designation: "Senior Architect"
      });
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
      if(nextState.age != this.state.age || netState.name = this.state.name) {
        return true;
      }
      return false;
  }

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

4. 懶加載組件

使用 Suspense 和 lazy。


import React, { lazy, Suspense } from "react";

export default class CallingLazyComponents extends React.Component {
  render() {

    var ComponentToLazyLoad = null;

    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>
    )
  }
}

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

我們可以根據(jù)具體的條件延遲組件加載,無(wú)需一開(kāi)始就加載兩個(gè)組件。


import React, { lazy, Suspense } from "react";

export default class UserSalutation extends React.Component {

    render() {
        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>
            )
        }
    }
}

5. 使用 React Fragments 避免額外標(biāo)記

6. 不要使用內(nèi)聯(lián)函數(shù)定義


<input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value="Click For Inline Function" />

如果我們使用內(nèi)聯(lián)函數(shù),則每次調(diào)用“render”函數(shù)時(shí)都會(huì)創(chuàng)建一個(gè)新的函數(shù)實(shí)例。

當(dāng) React 進(jìn)行虛擬 DOM diffing 時(shí),它每次都會(huì)找到一個(gè)新的函數(shù)實(shí)例;因此在渲染階段它會(huì)會(huì)綁定新函數(shù)并將舊實(shí)例扔給垃圾回收。

因此直接綁定內(nèi)聯(lián)函數(shù)就需要額外做垃圾回收和綁定到 DOM 的新函數(shù)的工作。

7. 避免componentWillMount()中的異步請(qǐng)求

因?yàn)檎?qǐng)求是異步的,首次render時(shí)數(shù)據(jù)也是空的,而且該鉤子函數(shù)中無(wú)法獲取頁(yè)面dom

注意:React 16.3 不推薦使用 componentWillMount。如果你使用的是最新版本的 React,請(qǐng)避免使用這個(gè)生命周期事件。

8. 在 Constructor 的早期綁定函數(shù)

當(dāng)我們?cè)?React 中創(chuàng)建函數(shù)時(shí),我們需要使用 bind 關(guān)鍵字將函數(shù)綁定到當(dāng)前上下文。

綁定可以在構(gòu)造函數(shù)中完成,也可以在我們將函數(shù)綁定到 DOM 元素的位置上完成。

兩者之間似乎沒(méi)有太大差異,但性能表現(xiàn)是不一樣的。

import React from "react";

export default class DelayedBinding extends React.Component {
  constructor() {
    this.state = {
      name: "Mayank"
    }
  }

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

  render() {
    return (
      <>
        <input type="button" value="Click" onClick={this.handleButtonClick.bind(this)} />
      </>
    )
  }
}

在上面的代碼中,我們?cè)?render 函數(shù)的綁定期間將函數(shù)綁定到按鈕上。

上面代碼的問(wèn)題在于,每次調(diào)用 render 函數(shù)時(shí)都會(huì)創(chuàng)建并使用綁定到當(dāng)前上下文的新函數(shù),但在每次渲染時(shí)使用已存在的函數(shù)效率更高。優(yōu)化方案如下:

......
constructor() {
    this.state = {
      name: "Mayank"
    }
    this.handleButtonClick = this.handleButtonClick.bind(this)
  }

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

9. 箭頭函數(shù)與構(gòu)造函數(shù)中的綁定

處理類(lèi)時(shí)的標(biāo)準(zhǔn)做法就是使用箭頭函數(shù)。使用箭頭函數(shù)時(shí)會(huì)保留執(zhí)行的上下文。

箭頭函數(shù)好處多多,但也有缺點(diǎn)。

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

每個(gè)組件都會(huì)有這些函數(shù)的一份實(shí)例,影響了可復(fù)用性。此外因?yàn)樗菍?duì)象屬性而不是原型屬性,所以這些函數(shù)在繼承鏈中不可用。

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

10. 避免使用內(nèi)聯(lián)樣式屬性

使用內(nèi)聯(lián)樣式時(shí)瀏覽器需要花費(fèi)更多時(shí)間來(lái)處理腳本和渲染,因?yàn)樗仨氂成鋫鬟f給實(shí)際 CSS 屬性的所有樣式規(guī)則。

import React from "react";

export default class InlineStyledComponents extends React.Component {
  render() {
    return (
        <b style={{"backgroundColor": "blue"}}>Welcome to Sample Page</b>
    )
  }
}

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

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

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

11. 使用唯一鍵迭代

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

使用 index 作為鍵就不會(huì)出現(xiàn)標(biāo)識(shí)不唯一的問(wèn)題了,因?yàn)?index 只會(huì)標(biāo)識(shí)所渲染的組件。

我們可以在以下場(chǎng)景中使用 index 作為鍵:
1.列表項(xiàng)是靜態(tài)的,項(xiàng)目不隨時(shí)間變化。
2.Items 沒(méi)有唯一 ID。
3.List 永遠(yuǎn)不會(huì)重新排序或過(guò)濾。
4.不會(huì)從頂部或中間添加或刪除項(xiàng)目。

12.事件節(jié)流和防抖

13.使用 CDN

使用 CDN 有以下好處:

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

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

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

14.使用 Web Workers 處理 CPU 密集任務(wù)

JavaScript 是一個(gè)單線(xiàn)程應(yīng)用,但在渲染網(wǎng)頁(yè)時(shí)需要執(zhí)行多個(gè)任務(wù):

處理 UI 交互、處理響應(yīng)數(shù)據(jù)、操縱 DOM 元素、啟用動(dòng)畫(huà)等。所有這些任務(wù)都由單個(gè)線(xiàn)程處理。

可以使用 worker 來(lái)分擔(dān)主線(xiàn)程的負(fù)載。

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

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

它們?cè)诟綦x環(huán)境中執(zhí)行,并且使用進(jìn)程間線(xiàn)程通信與主線(xiàn)程交互。主線(xiàn)程就可以騰出手來(lái)處理渲染和 DOM 操作任務(wù)。

參考文章:https://mp.weixin.qq.com/s/iZqV6GAi5zyX5P48hR4VLA##

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,442評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,835評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,581評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,922評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,096評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,374評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,591評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,789評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,196評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,524評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,322評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,554評(píng)論 2 379

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