React組件性能提升實現(xiàn)方法詳解
react組件的性能優(yōu)化的核心是減少渲染真實DOM節(jié)點的頻率,減少Virtual DOM比對的頻率。
組件卸載前執(zhí)行清理操作
在組件中為window 注冊的全局事件,以及定時器,在組件卸載前要清理掉。防止組件卸載后繼續(xù)執(zhí)行影響應用性能。
import React from 'react'
import { useEffect } from 'react'
import { observer } from 'mobx-react-lite'
function TestAdvance () {
useEffect(() => {
let timer = setInterval(() => {
console.log('定時器被觸發(fā)了')
}, 1000)
// 返回一個卸載時會被觸發(fā)的函數(shù)來對timer進行清理
return () => clearInterval(timer)
}, [])
return <div>Test</div>
}
export default observer(TestAdvance)通過純組件提升組件性能(類組件)
什么是純組件(PureComponent)
純組件會對組件輸入的數(shù)據(jù)進行淺層比較,如果當前輸入數(shù)據(jù)和上次輸入數(shù)據(jù)相同,組件不會重新渲染。
什么是淺層比較
- 比較引用數(shù)據(jù)類型在內(nèi)存中的引用地址是否相同;
- 比較基本數(shù)據(jù)類型的值是否相同。
如何實現(xiàn)純組件
- 類組件集成 PureComponent 類
- 函數(shù)組件使用 memo 方法
import React from 'react'
class App extends React.Component {
constructor () {
super()
this.state = {
person: { name: '張三', age: 20, job: 'waiter' }
}
}
updateName () {
setInterval(() => {
this.setState({ person: { ...this.state.person, name: '張三' } })
}, 3000)
}
componentDidMount () {
console.log('componentDidMount')
this.updateName()
}
render () {
return (
<div>
<RegularComponent name={this.state.person.name} />
<PureComponent name={this.state.person.name} />
</div>
)
}
}
class RegularComponent extends React.Component {
render () {
console.log('RegularComponent')
return <div>{this.props.name}</div>
}
}
class PureComponent extends React.PureComponent {
render () {
console.log('PureComponent')
return <div>{this.props.name}</div>
}
}
export default App淺層比較和深度diff的性能對比,為什么需要先進行淺層比較,而不直接進行diff比較呢。
和進行diff 比較相比,淺層比較將消耗更少的性能。diff 操作會重新遍歷整棵 Virtual DOM樹,而淺層比較只操作當前組件的 state 和 props。

可以看到PureComponent 當狀態(tài)值沒有改變時是不會被重新渲染的。
通過shouldComponentUpdate生命周期函數(shù)提升組件性能
shouldComponentUpdate是類組件當中的一個生命周期函數(shù),它允許我們在這個方法當中通過返回true 或者 false,來決定是否要重新渲染組件。
純組件只能進行淺層比較,要進行深層比較的話,需要使用到shouldComponentUpdate,它用于編寫自定義比較邏輯。
函數(shù)的第一個參數(shù)時nextProps, 第二個參數(shù)是nextState。
在內(nèi)存中當中有兩個對象,即使這兩個對象的長得一摸一樣,實際上它們有不同的引用地址,這個時候再怎么比較他們都不相同。
在我們的自定義邏輯中,如果返回true,則需要重新渲染組件;如果返回false,則不需要重現(xiàn)渲染組件了。
import React from 'react'
class App extends React.Component {
constructor () {
super()
this.state = {
person: { name: '張三', age: 20, job: 'waiter' }
}
}
updateName () {
setInterval(() => {
this.setState({ person: { ...this.state.person, job: 'Writer' } })
}, 3000)
}
componentDidMount () {
console.log('componentDidMount')
this.updateName()
}
/* shouldComponentUpdate (nextProps, nextState) {
if (
nextState.person.age !== this.state.person.age ||
nextState.person.name !== this.state.person.name
) {
return true
} else {
return false
}
} */
render () {
return (
<div>
<RegularComponent name={this.state.person.name} />
<PureComponent name={this.state.person.name} />
</div>
)
}
}
class RegularComponent extends React.Component {
render () {
console.log('RegularComponent')
return <div>{this.props.name}</div>
}
shouldComponentUpdate (nextProps, nextState) {
console.log(this.props, nextProps)
if (nextProps.name !== this.props.name) {
return true
} else {
return false
}
}
}
class PureComponent extends React.PureComponent {
render () {
console.log('PureComponent')
return <div>{this.props.name}</div>
}
}
export default App函數(shù)組件使用memo 減少渲染次數(shù)
memo的基本使用
將函數(shù)組件變?yōu)榧兘M件,將當前props和上一次的props進行淺層比較,如果相同就阻止組件重新渲染。
// 未使用memo方法包裹組件
import React, { useEffect, useState } from 'react'
function App () {
const [name] = useState('張三')
const [index, setIndex] = useState(0)
useEffect(() => {
let timer = setInterval(() => {
setIndex(prev => prev + 1)
}, 1000)
return () => {
clearInterval(timer)
}
}, [index])
return (
<div>
{index}
<ShowName name={name} />
</div>
)
}
function ShowName ({ name }) {
console.log('render...')
return <div>{name}</div>
}
export default App
使用memo封裝子組件,讓子組件減少不必要的渲染。
// 使用memo封裝子組件
import React, { memo, useEffect, useState } from 'react'
function App () {
const [name] = useState('張三')
const [index, setIndex] = useState(0)
useEffect(() => {
let timer = setInterval(() => {
setIndex(prev => prev + 1)
}, 1000)
return () => {
clearInterval(timer)
}
}, [index])
return (
<div>
{index}
<ShowName name={name} />
</div>
)
}
const ShowName = memo(function ({ name }) {
console.log('render...')
return <div>{name}</div>
})
export default App
可以看到僅初次渲染時,渲染了一次。
為memo 方法傳遞自定義比較邏輯
在memo方法內(nèi)部,其實也是進行的淺層比較。這個淺層比較對于引用數(shù)據(jù)類型來說,比較的是數(shù)據(jù)的引用地址。所以如果遇到引用數(shù)據(jù)類型的話,我們需要去傳遞自定義比較邏輯。
memo方法是可以接收第二個參數(shù)的,第二個參數(shù)是一個函數(shù),我們可以通過這個函數(shù)參數(shù)來編寫我們自定義比較邏輯。該函數(shù)參數(shù)接收兩個參數(shù),分別是prevProps和nextProps。
與shouldComponentUpdate的返回值邏輯相反,返回true,則不重新渲染;返回false的話則需要重新渲染。如果想讓這個組件重新渲染的話就返回false,否則返回true。
// 給memo傳遞第二個參數(shù),自定義比較邏輯
import React, { memo, useEffect, useState } from 'react'
function App () {
const [person, setPerson] = useState({ name: '張三', age: 20, job: 'waiter' })
const [index, setIndex] = useState(0)
useEffect(() => {
let timer = setInterval(() => {
setIndex(prev => prev + 1)
setPerson({ ...person, job: 'chef' })
}, 1000)
return () => {
clearInterval(timer)
}
}, [index, person])
return (
<div>
{index}
<ShowName person={person} />
</div>
)
}
function compare (prevProps, nextProps) {
if (
prevProps.person.name !== nextProps.person.name ||
prevProps.person.age !== nextProps.person.age
) {
return false
}
return true
}
const ShowName = memo(function ({ person }) {
console.log('render...')
return (
<div>
{person.name} {person.age}
</div>
)
}, compare)
export default App
通過組件懶加載提供應用性能
使用組件懶加載,可以減少bundle 文件大小,加快組件呈遞速度。
路由組件懶加載
import React, { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
// import Home from './pages/Home'
// import List from './pages/List'
// import NotFound from './pages/NotFound'
const Home = lazy(() => import(/* webpackChunkName: "Home" */ './pages/Home'))
const List = lazy(() => import(/* webpackChunkName: "List" */ './pages/List'))
const NotFound = lazy(() => import('./pages/NotFound'))
function App () {
return (
<BrowserRouter>
<Link to='/'>首頁 </Link>
<Link to='/list'>列表頁</Link>
<Suspense fallback={<div>loading...</div>}>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/list' element={<List />} errorElement={<NotFound />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}
export default App
可以看到,List頁面chunk 只有在頁面被加載渲染時才被被請求下載。
根據(jù)條件進行組件懶加載
適用于組件不會隨條件頻繁切換。
import React, { lazy } from 'react'
import { Suspense } from 'react'
function Test () {
let LazyComponent = null
if (false) {
LazyComponent = lazy(() =>
import(/* webpackChunkName: "Home-Test" */ './Home')
)
} else {
LazyComponent = lazy(() =>
import(/* webpackChunkName: "List-Test" */ './List')
)
}
return (
<Suspense fallback={<div>Test loading...</div>}>
<LazyComponent />
</Suspense>
)
}
export default Test通過使用占位符標記提升React組件的渲染性能
使用Fragment 避免額外標記
React組件中返回的jsx 如果有多個同級元素,多個同級元素必須要有一個共同的父級。
<div> ... </div> // 上面會多出一個無意義標記 // 應該改為 <fragment> ... </fragment> // 或者寫成下面這樣也是可以的 <> ... </>
為了滿足上面的條件,我們通常都會在最外層添加一個div,但是這樣的話就會多出一個無意義的標記。如果每個組件都多出這樣的一個無意義標記的話,瀏覽器渲染引擎的負擔就會加劇。
為了解決這個問題,react推出了fragment占位符標記。使用占位符標記,既滿足了擁有共同的父級的要求,又不會多出額外的無意義標記。
另外,fragment標記對也可以簡寫成 :<></>
通過避免使用內(nèi)聯(lián)函數(shù)提升組件性能
因為在使用內(nèi)聯(lián)函數(shù)后,render 方法每次進行時都會創(chuàng)建該函數(shù)的新實例,導致 React 在進行Virtual DOM比對時,新舊函數(shù)比對不相等,導致總是為元素綁定新的函數(shù)實例,而舊的函數(shù)實例又要交給垃圾回收器處理。
正確的做法是:在組件中單獨定義函數(shù),將函數(shù)綁定給事件。
render(){return(<input onChange={e => this.setState({inputValue: e.target.value})} />)}
// 在類組件中,應該采用下面的方式來改寫從而避免該元素被重新渲染
setInputvalue = e => {
this.setState({inputValue: e.target.value})
}
render(){
return (<input onChange={this.setInputValue} />)
}這樣一來,無論render方法被重新執(zhí)行多少次,類的屬性是不會發(fā)生變化的,所以在這個地方即使render方法被重新執(zhí)行n次,那它每次都不會產(chǎn)生新的函數(shù)實例,所以它每次不會給onChange去添加新的函數(shù)。
在構造函數(shù)中進行this指向的更正
在類組件中如果使用fn(){}這種方式定義函數(shù),函數(shù)this默認指向 undefined 。也就是說函數(shù)內(nèi)部的 this 指向需要被更正。
可以在構造函數(shù)中對函數(shù)的this 進行更正,也可以在行內(nèi)進行更正。兩者看起來沒有太大的區(qū)別,但是對性能的影響是不同的。
對于行內(nèi)更正來說,每一次render方法在執(zhí)行的時候它都會調(diào)用bind方法生成新的函數(shù)實例,也就是上邊提到的內(nèi)聯(lián)函數(shù)對性能的影響是一樣的。
因此比較推薦的是在構造函數(shù)當中去更正this的指向。因為構造函數(shù)只執(zhí)行一次,也就是函數(shù)的this 指向只更正一次,效率較高。
import React, { Component } from 'react'
export default class index extends Component {
constructor () {
super()
// 方法一:(推薦使用)
// 構造函數(shù)只執(zhí)行一次,所以函數(shù)this 指向更正的代碼也只執(zhí)行一次。
this.handleClick = this.handleClick.bind(this)
}
handleClick () {
console.log(this)
}
handleClick2= () {
console.log(this)
}
render () {
// 方式二:跟內(nèi)聯(lián)函數(shù)類似,不推薦,應避免使用
// 問題:render 放啊每次執(zhí)行時都會調(diào)用bind方法生成新的函數(shù)實例。
return <button onClick={this.handleClick.bind(this)}>按鈕</button>
}
}類組件中的箭頭函數(shù)
在類組件中使用箭頭函數(shù)不會存在this 指向問題。因為箭頭函數(shù)本身并不綁定this.
handleClick2= ()=> console.log(this)
箭頭函數(shù)在this 指向問題上是比較占優(yōu)勢的,但是同時也有不利的一面。
當使用箭頭函數(shù)時,該函數(shù)被添加為類的實例對象屬性,而不是原型對象屬性。如果組件被多次重用,每個組件實例對象中都會有一個相同的函數(shù)實例,降低了函數(shù)實例的可重用性,造成了資源浪費。
綜上所述, 更正函數(shù)內(nèi)部this 指向的最佳做法是: 在構造函數(shù)中使用 bind 方法進行綁定。 10. 避免使用內(nèi)聯(lián)樣式屬性以提升組件性能
當使用內(nèi)聯(lián)style 為元素添加樣式時,內(nèi)聯(lián)style 會被編譯成 JavaScript 代碼,通過JavaScript代碼將樣式規(guī)則映射到元素的身上,瀏覽器就會花費更多的時間執(zhí)行腳本和渲染UI,從而增加了組件的渲染時間。
// 例如:這段代碼的樣式會涉及到腳本的執(zhí)行,效率低,資源開銷大
<div style={{background: 'red'}}>Style in line</div>// 例如:這段代碼的樣式會涉及到腳本的執(zhí)行,效率低,資源開銷大
<div style={{background: 'red'}}>Style in line</div>更好的辦法是:
將CSS 文件導入樣式組件,能通過CSS直接做的事情就不要通過JavaScript去做。因為JavaScript操作DOM非常慢。而CSS默認開啟了GPU的渲染加速,更加高效。
優(yōu)化條件渲染以提升組件性能
頻繁的掛載和卸載組件,是一項耗性能的操作。為了確保應用程序的性能,應該減少組件掛載和卸載的次數(shù)。
在react中,我們經(jīng)常會根據(jù)條件渲染不同的組件,條件渲染是一項必做的優(yōu)化操作。
import React from 'react'
import Home from './Home'
import List from './List'
import Test from './Test'
function App () {
if (true) {
return (
<>
<Test />
<Home />
<List />
</>
)
} else {
return (
<>
<Home />
<List />
</>
)
}
}
export default App在上面的代碼中,顯然這種大范圍的條件渲染不太合理,存在優(yōu)化的空間,整個頁面隨著判斷條件改變而變化的部分只有Test 組件,因此,可以僅對Test組件進行條件渲染判斷,從而減少不必要的組件卸載和掛載的次數(shù)。
import React from 'react'
import Home from './Home'
import List from './List'
import Test from './Test'
function App () {
return (
<>
{true && <Test />}
<Home />
<List />
</>
)
}
export default App避免重復的無限渲染
當應用程序狀態(tài)發(fā)生更改時,react 就會調(diào)用render 方法,如果在render 方法中繼續(xù)更改應用程序狀態(tài),就會發(fā)生render 方法遞歸調(diào)用導致應用報錯。
import React, { Component } from 'react'
export default class index extends Component {
constructor () {
super()
this.state = { name: '張三' }
// 方法一:(推薦使用)
// 構造函數(shù)只執(zhí)行一次,所以函數(shù)this 指向更正的代碼也只執(zhí)行一次。
this.handleClick = this.handleClick.bind(this)
}
handleClick () {
console.log(this)
}
render () {
this.setState({ name: '李四' })
// 方式二:跟內(nèi)聯(lián)函數(shù)類似,不推薦,應避免使用
// 問題:render 放啊每次執(zhí)行時都會調(diào)用bind方法生成新的函數(shù)實例。
return <button onClick={this.handleClick.bind(this)}>按鈕</button>
}
}
與其他生命周期函數(shù)不同,render方法應該被作為純函數(shù)。
這意味著,在render方法中不要做以下的事情:
- 不要調(diào)用setState方法;
- 不要使用其他手段查詢更改原生DOM元素;
- 以及不要做其他更改應用程序的任何操作。
render 方法對的執(zhí)行要根據(jù)狀態(tài)的改變,這樣可以保持組件的行為和渲染方式一致。
所以,在react當中,不要在render方法當中,不要在componentWillUpdate這個生命周期函數(shù)當中,不要在componentDidUpdated這個生命周期函數(shù)當中繼續(xù)調(diào)用setState方法去更新狀態(tài)。否則將導致重復的無限渲染,應用程序崩潰。
為應用程序創(chuàng)建錯誤邊界
默認情況下,組件渲染錯誤會導致整個應用程序的中斷,創(chuàng)建錯誤邊界可確保在特定組件發(fā)生錯誤時應用程序不會中斷。從而增加應用程序的健壯性(魯棒性)。
錯誤邊界是一個React組件,可以捕獲子級組件在渲染時發(fā)生的錯誤,當錯誤發(fā)生時,可以將錯誤記錄下來,可以顯示備用UI界面。
錯誤邊界涉及兩個生命周期函數(shù),分別為:getDerivedStateFromError 和 componentDidCatch.
getDerivedStateFromError: 是一個靜態(tài)方法,方法中返回一個對象,該對象會和state 對象進行合并,用于更改應用程序的狀態(tài),從而給我們提供顯示備用UI界面的機會。
componentDidCatch:該方法用于記錄應用程序的錯誤信息,該方法的參數(shù)就是錯誤對象。
import React, { Component } from 'react'
import ErrorTrigger from '../components/ErrorTrigger'
export default class ErrorBoundaries extends Component {
constructor () {
super()
this.state = {
hasError: false
}
}
componentDidCatch (error) {
// 可以將程序錯誤信息記錄到遠端服務器
console.log('componentDidCatch')
}
static getDerivedStateFromError () {
console.log('getDerivedStateFromError')
return {
// 該返回對象會和state 對象進行合并
hasError: true
}
}
render () {
if (this.state.hasError) {
return <>我是備用UI界面</>
}
return (
<>
<ErrorTrigger />
</>
)
}
}import React, { Component } from 'react'
export class ErrorTrigger extends Component {
render () {
throw new Error('錯誤邊界內(nèi)發(fā)生錯誤了')
return <div>ErrorTrigger</div>
}
}
export default ErrorTrigger注意??:錯誤邊界不能捕獲異步錯誤,例如點擊按鈕時發(fā)生的錯誤。


避免數(shù)據(jù)結構突變
組件中的props和state 的數(shù)據(jù)結構應該保持一致,數(shù)據(jù)結構突變會導致輸出不一致。
import React, { Component } from 'react'
export default class App extends Component {
constructor () {
super()
this.state = {
person: {
name: '張三',
age: 20,
job: 'waiter'
}
}
}
render () {
const { name, age, job } = this.state.person
return (
<>
<p>
{name} {age} {job}
</p>
<button
onClick={() =>
this.setState({
...this.state,
person: {
age: 30
}
})
}
>
更新信息
</button>
</>
)
}
}
點擊更新信息按鈕前

點擊更新信息按鈕后
這是因為數(shù)據(jù)狀態(tài)在發(fā)生更改時,發(fā)生了數(shù)據(jù)結構突變導致的數(shù)據(jù)丟失。
使用setState更改狀態(tài)代碼應該修改為:
this.setState({
...this.state,
// 這里的person 數(shù)據(jù)結構應該和原來的保持一致,避免數(shù)據(jù)丟失
// person: {
// age: 30
// }
person: {
...this.state.person,
age: 30
}
})
修改更新數(shù)據(jù)保持結構一致的代碼后點擊更新信息按鈕后的顯示
優(yōu)化依賴項大小
在程序應用開發(fā)中,常常會依賴第三方包,但我們不想引用包中所有的代碼,我們只想用到哪些代碼就包含哪些代碼。
此時,可以使用插件對依賴項進行優(yōu)化,優(yōu)化資源。
拿lodash舉例:
應用基于create-react-app 腳手架創(chuàng)建:
下載依賴
npm install react-app-rewired customize-cra lodash babel-plugin-lodash
react-app-rewired: 覆蓋create-react-app的默認配置
module.exports = function(oldConfig){
return newConfig
}
// 參數(shù)中的oldConfig 就是默認的webpack configcustomize-cra: 導出了一些輔助方法,可以讓以上寫法更加簡潔。
const {override, useBabelRc} from 'customize-cra'
module.exports = override(
(oldConfig) => newConfig,
(oldConfig) => newConfig
)
// override 可以接收多個參數(shù),每個參數(shù)都是一個配置函數(shù),函數(shù)接收oldConfig,返回 newConfig
// useBabelRc 允許使用 .babelrc 文件進行 babel 配置babel-plugin-lodash: 對應用中的lodash 進行精簡
在項目根目錄下新建 config-overrides.js 并加入配置代碼
const {override, useBabelRc} from 'customize-cra'
module.exports = override(useBabelRc())
// 這里使用 .babelrc 文件進行 babel 配置修改package.json 中的文件構建命令
把原來命令中的 react-scripts 改為 react-app-rewired
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},創(chuàng)建 .babelrc 文件并加入配置
{
"plugins":["lodash"]
}
命令打包生成的生產(chǎn)環(huán)境下的三種js文件
main.[hash].chunk.js 主應用程序代碼,App.js 等
1.[hash].chunk.js 第三方依賴包的代碼,包含在node_modules中導入的模塊
runtime~main.[hash].js webpack的運行時代碼
優(yōu)化前后結果比對:

到此這篇關于React組件性能提升實現(xiàn)方法詳解的文章就介紹到這了,更多相關React組件性能優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
關于React Native使用axios進行網(wǎng)絡請求的方法
axios是一個基于Promise的Http網(wǎng)絡庫,可運行在瀏覽器端和Node.js中,Vue應用的網(wǎng)絡請求基本都是使用它完成的。這篇文章主要介紹了React Native使用axios進行網(wǎng)絡請求,需要的朋友可以參考下2021-08-08
React自定義hooks同步獲取useState的最新狀態(tài)值方式
這篇文章主要介紹了React自定義hooks同步獲取useState的最新狀態(tài)值方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
react?hooks?d3實現(xiàn)企查查股權穿透圖結構圖效果詳解
這篇文章主要為大家介紹了react?hooks?d3實現(xiàn)企查查股權穿透圖結構圖效果詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01
React中hook函數(shù)與useState及useEffect的使用
這篇文章主要介紹了React中hook函數(shù)與useState及useEffect的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-10-10
關于React16.0的componentDidCatch方法解讀
這篇文章主要介紹了關于React16.0的componentDidCatch方法解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
React中的useEffect useLayoutEffect到底怎么用
這篇文章主要介紹了React中的useEffect useLayoutEffect具體使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-02-02
React中memo useCallback useMemo方法作用及使用場景
這篇文章主要為大家介紹了React中三個hooks方法memo useCallback useMemo的作用及使用場景示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助2023-03-03

