本文探討React中的無狀態組件。這類組件不包含this.state = { ... }
調用,僅處理傳入的“props”和子組件。
要點總結
this.state = { … }
調用。它們只處理傳入的“props”和子組件,這使得它們更簡單,也更容易從測試的角度進行分析。 shouldComponentUpdate
方法,可以對每個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> } }
代碼運行正常。它非常基礎,但建立了示例。
需要注意的是:
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來救援!
首先,讓我們回顧一下我們的目標:
(實際上,我們理想的情況是組件只渲染一次。為什麼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(name
和highlighted
鍵)沒有改變。
讓我們更進一步。我們不使用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> }
運行這段代碼後,你會注意到,只有當name
或highlighted
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性能的常見問題
狀態組件(也稱為類組件)維護關於組件狀態隨時間變化的內存。它們負責組件的行為和渲染方式。另一方面,無狀態組件(也稱為函數組件)沒有自己的狀態。它們以props的形式從父組件接收數據並渲染它。它們主要負責組件的UI部分。
無狀態組件可以顯著提高React應用程序的性能。由於它們不管理自己的狀態或生命週期方法,因此它們的代碼更少,這使得它們更容易理解和測試。它們還可以減少應用程序消耗的內存量。為了優化性能,你可以盡可能地將狀態組件轉換為無狀態組件,並使用React的PureComponent
來避免不必要的重新渲染。
PureComponent
是一種特殊的React組件,可以幫助優化應用程序的性能。它使用淺層prop和狀態比較來實現shouldComponentUpdate
生命週期方法。這意味著它只有在狀態或props發生變化時才會重新渲染,這可以顯著減少不必要的重新渲染並提高性能。
將狀態組件轉換為無狀態組件包括從組件中刪除任何狀態或生命週期方法。組件應該只通過props接收數據並渲染它。這是一個例子:
// 狀態組件 class Welcome extends React.Component { render() { return
// 無狀態組件 function Welcome(props) { return
在React中使用無狀態組件的一些最佳實踐包括:盡可能多地使用它們來提高性能和可讀性;保持它們小巧,專注於單一功能;避免使用狀態或生命週期方法。還建議使用props的解構來編寫更簡潔的代碼。
不可以,無狀態組件不能在React中使用生命週期方法。生命週期方法只適用於類組件。但是,隨著React 16.8中Hooks的引入,你現在可以向函數組件添加狀態和生命週期特性。
雖然無狀態組件有很多優點,但它們也有一些局限性。它們不能使用生命週期方法或管理自己的狀態。但是,這些限制可以通過使用React Hooks來克服。
無狀態組件通過簡潔明了來提高代碼的可讀性。它們不管理自己的狀態或使用生命週期方法,這使得它們更簡單、更容易理解。它們還鼓勵使用小型、可重用的組件,這可以使代碼更井然有序,更容易維護。
可以,無狀態組件可以在React中處理事件。事件處理程序可以作為props傳遞給無狀態組件。這是一個例子:
function ActionLink(props) { return ( Click me ); }
無狀態組件通過促進使用小型、可重用的組件來促進代碼的模塊化。每個組件都可以獨立開發和測試,這使得代碼更易於維護和理解。它還鼓勵關注點分離,其中每個組件負責單一功能。
以上是通過無狀態組件優化反應性能的詳細內容。更多資訊請關注PHP中文網其他相關文章!