首頁 > web前端 > js教程 > 通過無狀態組件優化反應性能

通過無狀態組件優化反應性能

Lisa Kudrow
發布: 2025-02-16 11:35:08
原創
259 人瀏覽過

Optimizing React Performance with Stateless Components

Optimizing React Performance with Stateless Components

本文探討React中的無狀態組件。這類組件不包含this.state = { ... }調用,僅處理傳入的“props”和子組件。

要點總結

  • React中的無狀態組件不包含任何this.state = { … }調用。它們只處理傳入的“props”和子組件,這使得它們更簡單,也更容易從測試的角度進行分析。
  • 無狀態組件可以轉換為函數式組件,這更像是純JavaScript,對所使用的框架依賴性更小。
  • 通過使用React.PureComponent可以優化無狀態組件的性能,它具有內置的shouldComponentUpdate方法,可以對每個prop進行淺比較。
  • Recompose是一個用於React中函數組件和高階組件的實用工具庫。它可以用來渲染函數組件,而不會在props不變時重新渲染。
  • 無狀態組件可以顯著提高React應用程序的性能,因為它們不管理自己的狀態或生命週期方法,從而減少了代碼量和內存消耗。但是,它們也有一些限制,例如不能使用生命週期方法或管理自己的狀態。

基礎知識

import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}}
         onClick={event => {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

代碼運行正常。它非常基礎,但建立了示例。

需要注意的是:

  • 它是無狀態的,沒有this.state = { ... }
  • console.log用於了解其使用情況。特別是在進行性能優化時,如果props實際上沒有改變,則需要避免不必要的重新渲染。
  • 這裡的事件處理程序是“內聯”的。這種語法很方便,因為它的代碼靠近它處理的元素,而且這種語法意味著你不需要進行任何.bind(this)操作。
  • 使用這樣的內聯函數,會有一點性能損失,因為函數必須在每次渲染時創建。稍後將詳細介紹這一點。

展示型組件

我們現在意識到上面的組件不僅是無狀態的,它實際上是Dan Abramov所說的展示型組件。它只是一個名稱,但基本上,它很輕量級,產生一些HTML/DOM,並且不處理任何狀態數據。

所以我們可以把它做成一個函數!這不僅感覺“很酷”,而且也使它不那麼可怕,因為它更容易理解。它接收輸入,並且獨立於環境,總是返回相同的輸出。當然,它“回調”,因為其中一個prop是一個可調用的函數。

讓我們重寫它:

import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}}
         onClick={event => {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

感覺不錯吧?它感覺像是純JavaScript,你可以編寫它而無需考慮你正在使用的框架。

持續重新渲染的問題

假設我們的User組件用在一個隨時間變化狀態的組件中。但是狀態不會影響我們的組件。例如,像這樣:

const User = ({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}}
       onClick={event => {
         userSelected()
       }}>
      {name}
    </h3>
  </div>
}
登入後複製
登入後複製
登入後複製

運行這段代碼,你會注意到,即使什麼都沒有改變,我們的組件也會重新渲染!現在這還不是什麼大問題,但在實際應用中,組件往往會越來越複雜,每一次不必要的重新渲染都會導致網站速度變慢。

如果你現在用react-addons-perf調試這個應用,我肯定你會發現時間浪費在渲染Users->User上了。糟糕!該怎麼辦? !

似乎一切都在表明我們需要使用shouldComponentUpdate來覆蓋React如何認為props不同,而我們確信它們並沒有不同。為了添加React生命週期鉤子,組件需要成為一個類。 。所以我們回到最初的基於類的實現,並添加新的生命週期鉤子方法:

回到類組件

import React, { Component } from 'react'

class Users extends Component {
  constructor(props) {
    super(props)
    this.state = {
      otherData: null,
      users: [{name: 'John Doe', highlighted: false}]
    }
  }

  async componentDidMount() {
    try {
      let response = await fetch('https://api.github.com')
      let data = await response.json()
      this.setState({otherData: data})
    } catch(err) {
      throw err
    }
  }

  toggleUserHighlight(user) {
    this.setState(prevState => ({
      users: prevState.users.map(u => {
        if (u.name === user.name) {
          u.highlighted = !u.highlighted
        }
        return u
      })
    }))
  }

  render() {
    return <div>
      <h1>Users</h1>
      {
        this.state.users.map(user => {
          return <User
            name={user.name}
            highlighted={user.highlighted}
            userSelected={() => {
              this.toggleUserHighlight(user)
            }}/>
         })
      }
    </div>
  }
}
登入後複製

注意新增的shouldComponentUpdate方法。這有點難看。我們不僅不能再使用函數,而且還必須手動列出可能更改的props。這涉及到一個大膽的假設,即userSelected函數prop不會改變。這不太可能,但需要注意。

但是請注意,即使包含的App組件重新渲染,這也只渲染一次!所以,這對性能有好處。但是我們能做得更好嗎?

React.PureComponent

從React 15.3開始,有一個新的組件基類。它被稱為PureComponent,它有一個內置的shouldComponentUpdate方法,它對每個prop進行“淺比較”。太棒了!如果我們使用這個,我們可以丟棄我們自定義的shouldComponentUpdate方法,該方法必須列出特定的props。

import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}}
         onClick={event => {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

嘗試一下,你會失望的。它每次都會重新渲染。為什麼? !答案是因為userSelected函數在App的render方法中每次都會重新創建。這意味著當基於PureComponent的組件調用它自己的shouldComponentUpdate()時,它會返回true,因為該函數總是不同的,因為它每次都會被創建。

通常情況下,解決這個問題的方法是在包含組件的構造函數中綁定該函數。首先,如果我們這樣做,這意味著我們必須鍵入方法名5次(而之前是1次):

  • this.userSelected = this.userSelected.bind(this)(在構造函數中)
  • userSelected() { ... }(作為方法定義本身)
  • <User ... userSelected={this.userSelected} ... />(在定義User組件渲染的地方)

另一個問題是,正如你所看到的,當實際執行userSelected方法時,它依賴於一個閉包。特別是它依賴於來自this.state.users.map()迭代器的作用域變量user

誠然,這個問題有一個解決方案,那就是首先將userSelected方法綁定到this,然後當調用該方法(在子組件中)時,將user(或其名稱)傳遞回去。這裡有一個這樣的解決方案。

recompose來救援!

首先,讓我們回顧一下我們的目標:

  1. 編寫函數式組件感覺更好,因為它們是函數。這立即告訴代碼閱讀者它不保存任何狀態。它們很容易從單元測試的角度進行推理。而且它們感覺更簡潔,更像純JavaScript(當然還有JSX)。
  2. 我們太懶了,不想綁定所有傳遞給子組件的方法。當然,如果方法很複雜,最好重構它們,而不是動態創建它們。動態創建方法意味著我們可以直接在它們使用的地方編寫它們的代碼,我們不必為它們命名,也不必在三個不同的地方提及它們5次。
  3. 子組件應該只在props改變時才重新渲染。對於小型快速的組件來說,這可能無關緊要,但對於實際應用來說,當你有許多這樣的組件時,所有這些額外的渲染都會在可以避免的情況下浪費CPU。

(實際上,我們理想的情況是組件只渲染一次。為什麼React不能為我們解決這個問題呢?那樣的話,“如何使React更快”的博客文章就會減少90%。)

根據文檔,recompose是“一個用於函數組件和高階組件的React實用工具庫。可以把它想像成React的lodash。”。這個庫有很多東西可以探索,但現在我們想渲染我們的函數組件,而不會在props沒有改變時重新渲染。

我們第一次嘗試用recompose.pure重寫它回到函數組件,看起來像這樣:

import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}}
         onClick={event => {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

你可能會注意到,如果你運行這段代碼,User組件仍然會重新渲染,即使props(namehighlighted鍵)沒有改變。

讓我們更進一步。我們不使用recompose.pure,而是使用recompose.onlyUpdateForKeys,它是recompose.pure的一個版本,但你可以明確地指定要關注的prop鍵:

const User = ({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}}
       onClick={event => {
         userSelected()
       }}>
      {name}
    </h3>
  </div>
}
登入後複製
登入後複製
登入後複製

運行這段代碼後,你會注意到,只有當namehighlighted props改變時,它才會更新。如果父組件重新渲染,User組件不會。

萬歲!我們找到了金子!

討論

首先,問問自己是否值得對組件進行性能優化。也許這比它值得的要多。你的組件應該很輕量級,也許你可以將任何昂貴的計算從組件中移出,並將它們移到外部的可記憶化函數中,或者你可以重新組織你的組件,以便在某些數據不可用時不浪費渲染組件。例如,在本例中,你可能不想在fetch完成之前渲染User組件。

以對你來說最方便的方式編寫代碼,然後啟動你的程序,然後從那裡迭代以使其性能更好,這不是一個壞的解決方案。在本例中,為了提高性能,你需要將函數組件的定義從:

import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}}
         onClick={event => {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}
登入後複製
登入後複製
登入後複製
登入後複製
登入後複製

…改為…

const User = ({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3 style={{fontStyle: highlighted ? 'italic' : 'normal'}}
       onClick={event => {
         userSelected()
       }}>
      {name}
    </h3>
  </div>
}
登入後複製
登入後複製
登入後複製

理想情況下,與其展示繞過問題的方法,最好的解決方案是React的一個新補丁,它對shallowEqual進行了巨大的改進,能夠“自動”地識別正在傳入和比較的是一個函數,並且僅僅因為它不相等並不意味著它實際上不同。

承認!對於不得不處理在構造函數中綁定方法和每次重新創建的內聯函數,存在一種折中的替代方案。那就是公共類字段。它是Babel中的一個第二階段特性,所以你的設置很可能支持它。例如,這裡有一個使用它的分支,它不僅更短,而且現在也意味著我們不需要手動列出所有非函數props。這個解決方案必須放棄閉包。儘管如此,當需要時,了解和意識到recompose.onlyUpdateForKeys仍然是好的。

更多關於React的信息,請查看我們的課程《React The ES6 Way》。

本文由Jack Franklin同行評審。感謝所有SitePoint的同行評審者,使SitePoint的內容盡善盡美!

關於使用無狀態組件優化React性能的常見問題

React中狀態組件和無狀態組件的主要區別是什麼?

狀態組件(也稱為類組件)維護關於組件狀態隨時間變化的內存。它們負責組件的行為和渲染方式。另一方面,無狀態組件(也稱為函數組件)沒有自己的狀態。它們以props的形式從父組件接收數據並渲染它。它們主要負責組件的UI部分。

如何使用無狀態組件優化我的React應用程序的性能?

無狀態組件可以顯著提高React應用程序的性能。由於它們不管理自己的狀態或生命週期方法,因此它們的代碼更少,這使得它們更容易理解和測試。它們還可以減少應用程序消耗的內存量。為了優化性能,你可以盡可能地將狀態組件轉換為無狀態組件,並使用React的PureComponent來避免不必要的重新渲染。

什麼是PureComponent,它如何幫助優化React性能?

PureComponent是一種特殊的React組件,可以幫助優化應用程序的性能。它使用淺層prop和狀態比較來實現shouldComponentUpdate生命週期方法。這意味著它只有在狀態或props發生變化時才會重新渲染,這可以顯著減少不必要的重新渲染並提高性能。

如何將React中的狀態組件轉換為無狀態組件?

將狀態組件轉換為無狀態組件包括從組件中刪除任何狀態或生命週期方法。組件應該只通過props接收數據並渲染它。這是一個例子:

// 狀態組件 class Welcome extends React.Component { render() { return

Hello, {this.props.name}
; } }

// 無狀態組件 function Welcome(props) { return

Hello, {props.name}
; }

在React中使用無狀態組件的最佳實踐是什麼?

在React中使用無狀態組件的一些最佳實踐包括:盡可能多地使用它們來提高性能和可讀性;保持它們小巧,專注於單一功能;避免使用狀態或生命週期方法。還建議使用props的解構來編寫更簡潔的代碼。

無狀態組件可以在React中使用生命週期方法嗎?

不可以,無狀態組件不能在React中使用生命週期方法。生命週期方法只適用於類組件。但是,隨著React 16.8中Hooks的引入,你現在可以向函數組件添加狀態和生命週期特性。

React中無狀態組件的局限性是什麼?

雖然無狀態組件有很多優點,但它們也有一些局限性。它們不能使用生命週期方法或管理自己的狀態。但是,這些限制可以通過使用React Hooks來克服。

無狀態組件如何提高代碼的可讀性?

無狀態組件通過簡潔明了來提高代碼的可讀性。它們不管理自己的狀態或使用生命週期方法,這使得它們更簡單、更容易理解。它們還鼓勵使用小型、可重用的組件,這可以使代碼更井然有序,更容易維護。

無狀態組件可以在React中處理事件嗎?

可以,無狀態組件可以在React中處理事件。事件處理程序可以作為props傳遞給無狀態組件。這是一個例子:

function ActionLink(props) { return ( Click me ); }

無狀態組件如何促進代碼的模塊化?

無狀態組件通過促進使用小型、可重用的組件來促進代碼的模塊化。每個組件都可以獨立開發和測試,這使得代碼更易於維護和理解。它還鼓勵關注點分離,其中每個組件負責單一功能。

以上是通過無狀態組件優化反應性能的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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