React?組件性能最佳優(yōu)化實(shí)踐分享
React 組件性能優(yōu)化最佳實(shí)踐
React 組件性能優(yōu)化的核心是減少渲染真實(shí) DOM 節(jié)點(diǎn)的頻率,減少 Virtual DOM 比對(duì)的頻率。如果子組件未發(fā)生數(shù)據(jù)改變不渲染子組件。
組件卸載前進(jìn)行清理操作
以下代碼在組件掛載時(shí)會(huì)創(chuàng)建一個(gè)interval組件銷毀后清除定時(shí)器,間隔1秒會(huì)觸發(fā)渲染count+1,組件銷毀后如果不清除定時(shí)器它會(huì)一直消耗資源
import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"
const App = () => {
let [index, setIndex] = useState(0)
useEffect(() => {
let timer = setInterval(() => {
setIndex(prev => prev + 1)
console.log('timer is running...')
}, 1000)
return () => clearInterval(timer)
}, [])
return (
<button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById("root"))}>
{index}
</button>
)
}
export default App每次數(shù)據(jù)更新都會(huì)觸發(fā)組件重新渲染,這里的優(yōu)化為:組件銷毀清理定時(shí)器

類組件使用純組件PureComponent
什么是純組件
純組件會(huì)對(duì)組件輸入數(shù)據(jù)進(jìn)行淺層比較,如果當(dāng)前輸入數(shù)據(jù)和上次輸入數(shù)據(jù)相同,組件不會(huì)重新渲染
什么是淺層比較
比較引用數(shù)據(jù)類型在內(nèi)存中的引用地址是否相同,比較基本數(shù)據(jù)類型的值是否相同。
為什么不直接進(jìn)行 diff 操作, 而是要先進(jìn)行淺層比較,淺層比較難道沒有性能消耗嗎
和進(jìn)行 diff 比較操作相比,淺層比較將消耗更少的性能。diff 操作會(huì)重新遍歷整顆 virtualDOM 樹, 而淺層比較只操作當(dāng)前組件的 state 和 props。
import React from "react"
export default class App extends React.Component {
constructor() {
super()
this.state = {name: "張三"}
}
updateName() {
setInterval(() => this.setState({name: "張三"}), 1000)
}
componentDidMount() {
this.updateName()
}
render() {
return (
<div>
<RegularComponent name={this.state.name} />
<PureChildComponent name={this.state.name} />
</div>
)
}
}
class RegularComponent extends React.Component {
render() {
console.log("RegularComponent")
return <div>{this.props.name}</div>
}
}
class PureChildComponent extends React.PureComponent {
render() {
console.log("PureChildComponent")
return <div>{this.props.name}</div>
}
}組件掛載以后會(huì)有一個(gè)定時(shí)器間隔1秒設(shè)置一次name,我們可以看到RegularComponent一直在渲染,即使數(shù)據(jù)沒有發(fā)生變化也會(huì)渲染。PureChildComponent只有一次渲染,因此使用純組件會(huì)對(duì)props state進(jìn)行進(jìn)行比較,數(shù)據(jù)相同不會(huì)重新渲染。

shouldComponentUpdate
純組件只能進(jìn)行淺層比較,要進(jìn)行深層比較,使用 shouldComponentUpdate,它用于編寫自定義比較邏輯。
返回 true 重新渲染組件,返回 false 阻止重新渲染。
函數(shù)的第一個(gè)參數(shù)為 nextProps, 第二個(gè)參數(shù)為 nextState。
import React from "react"
export default class App extends React.Component {
constructor() {
super()
this.state = {name: "張三", age: 20, job: "waiter"}
}
componentDidMount() {
setTimeout(() => this.setState({ job: "chef" }), 1000)
}
shouldComponentUpdate(nextProps, nextState) {
if (this.state.name !== nextState.name || this.state.age !== nextState.age) {
return true
}
return false
}
render() {
console.log("rendering")
let { name, age } = this.state
return <div>{name} {age}</div>
}
}即使繼承了Component的組件定時(shí)器一直修改數(shù)據(jù)也不會(huì)觸發(fā)重新渲染

純函數(shù)組件使用React.memo優(yōu)化性能
memo 基本使用
將函數(shù)組件變?yōu)榧兘M件,將當(dāng)前 props 和上一次的 props 進(jìn)行淺層比較,如果相同就阻止組件重新渲染。
import React, { memo, useEffect, useState } from "react"
function ShowName({ name }) {
console.log("showName render...")
return <div>{name}</div>
}
const ShowNameMemo = memo(ShowName)
function App() {
const [index, setIndex] = useState(0)
const [name] = useState("張三")
useEffect(() => {
setInterval(() => {
setIndex(prev => prev + 1)
}, 1000)
}, [])
return (
<div>
{index}
<ShowNameMemo name={name} />
</div>
)
}
export default Appmemo 傳遞比較邏輯
(使用 memo方法自定義比較邏輯,用于執(zhí)行深層比較。)
import React, { memo, useEffect, useState } from "react";
function ShowName({ person }) {
console.log("showName render...");
return (
<div>
{person.name}
丨
{person.job}
</div>
);
}
function comparePerson(prevProps, nextProps) {
if (
prevProps.person.name !== nextProps.person.name ||
prevProps.person.age !== nextProps.person.age
) {
return false
}
return true
}
const ShowNameMemo = memo(ShowName, comparePerson);
function App() {
const [person, setPerson] = useState({ name: "張三", job: "developer" });
useEffect(() => {
setInterval(() => {
setPerson((data) => ({ ...data, name: "haoxuan" }));
}, 1000);
}, []);
return (
<div>
<ShowNameMemo person={person} />
</div>
);
}
export default App;使用組件懶加載
使用組件懶加載可以減少 bundle 文件大小, 加快組件呈遞速度。
路由組件懶加載
import React, { lazy, Suspense } from "react"
import { BrowserRouter, Link, Route, Switch } from "react-router-dom"
const Home = lazy(() => import(/* webpackChunkName: "Home" */ "./Home"))
const List = lazy(() => import(/* webpackChunkName: "List" */ "./List"))
function App() {
return (
<BrowserRouter>
<Link to="/">Home</Link>
<Link to="/list">List</Link>
<Switch>
<Suspense fallback={<div>Loading</div>}>
<Route path="/" component={Home} exact />
<Route path="/list" component={List} />
</Suspense>
</Switch>
</BrowserRouter>
)
}
export default App根據(jù)條件進(jìn)行組件懶加載(適用于組件不會(huì)隨條件頻繁切換)
import React, { lazy, Suspense } from "react"
function App() {
let LazyComponent = null
if (true) {
LazyComponent = lazy(() => import(/* webpackChunkName: "Home" */ "./Home"))
} else {
LazyComponent = lazy(() => import(/* webpackChunkName: "List" */ "./List"))
}
return (
<Suspense fallback={<div>Loading</div>}>
<LazyComponent />
</Suspense>
)
}
export default App使用Fragment 避免額外標(biāo)記
為了滿足這個(gè)條件我們通常都會(huì)在最外層添加一個(gè)div, 但是這樣的話就會(huì)多出一個(gè)無(wú)意義的標(biāo)記, 如果每個(gè)組件都多出這樣的一個(gè)無(wú)意義標(biāo)記的話, 瀏覽器渲染引擎的負(fù)擔(dān)就會(huì)加劇。
import { Fragment } from "react"
function App() {
return (
<Fragment>
<div>message a</div>
<div>message b</div>
</Fragment>
)
}function App() {
return (
<>
<div>message a</div>
<div>message b</div>
</>
)
}不要使用內(nèi)聯(lián)函數(shù)定義
在使用內(nèi)聯(lián)函數(shù)后, render 方法每次運(yùn)行時(shí)都會(huì)創(chuàng)建該函數(shù)的新實(shí)例, 導(dǎo)致 React 在進(jìn)行 Virtual DOM 比對(duì)時(shí), 新舊函數(shù)比對(duì)不相等,導(dǎo)致 React 總是為元素綁定新的函數(shù)實(shí)例, 而舊的函數(shù)實(shí)例又要交給垃圾回收器處理。
錯(cuò)誤示范:
import React from "react"
export default class App extends React.Component {
constructor() {
super()
this.state = {
inputValue: ""
}
}
render() {
return (
<input
value={this.state.inputValue}
onChange={e => this.setState({ inputValue: e.target.value })}
/>
)
}
}正確的做法是在組件中單獨(dú)定義函數(shù), 將函數(shù)綁定給事件:
import React from "react"
export default class App extends React.Component {
constructor() {
super()
this.state = {
inputValue: ""
}
}
setInputValue = e => {
this.setState({ inputValue: e.target.value })
}
render() {
return (
<input value={this.state.inputValue} onChange={this.setInputValue} />
)
}
}在構(gòu)造函數(shù)中進(jìn)行函數(shù)this綁定
在類組件中如果使用 fn() {} 這種方式定義函數(shù), 函數(shù) this 默認(rèn)指向 undefined. 也就是說函數(shù)內(nèi)部的 this 指向需要被更正.
可以在構(gòu)造函數(shù)中對(duì)函數(shù)的 this 進(jìn)行更正, 也可以在行內(nèi)進(jìn)行更正, 兩者看起來(lái)沒有太大區(qū)別, 但是對(duì)性能的影響是不同的
export default class App extends React.Component {
constructor() {
super()
// 方式一
// 構(gòu)造函數(shù)只執(zhí)行一次, 所以函數(shù) this 指向更正的代碼也只執(zhí)行一次.
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log(this)
}
render() {
// 方式二
// 問題: render 方法每次執(zhí)行時(shí)都會(huì)調(diào)用 bind 方法生成新的函數(shù)實(shí)例.
return <button onClick={this.handleClick.bind(this)}>按鈕</button>
}
}類組件中的箭頭函數(shù)
在類組件中使用箭頭函數(shù)不會(huì)存在 this 指向問題, 因?yàn)榧^函數(shù)本身并不綁定 this。
export default class App extends React.Component {
handleClick = () => console.log(this)
render() {
return <button onClick={this.handleClick}>按鈕</button>
}
}箭頭函數(shù)在 this 指向問題上占據(jù)優(yōu)勢(shì), 但是同時(shí)也有不利的一面.
當(dāng)使用箭頭函數(shù)時(shí), 該函數(shù)被添加為類的實(shí)例對(duì)象屬性, 而不是原型對(duì)象屬性. 如果組件被多次重用, 每個(gè)組件實(shí)例對(duì)象中都將會(huì)有一個(gè)相同的函數(shù)實(shí)例, 降低了函數(shù)實(shí)例的可重用性造成了資源浪費(fèi).
綜上所述, 更正函數(shù)內(nèi)部 this 指向的最佳做法仍是在構(gòu)造函數(shù)中使用 bind 方法進(jìn)行綁定
優(yōu)化條件渲染
頻繁的掛載和卸載組件是一項(xiàng)耗性能的操作, 為了確保應(yīng)用程序的性能, 應(yīng)該減少組件掛載和卸載的次數(shù).
在 React 中我們經(jīng)常會(huì)根據(jù)條件渲染不同的組件. 條件渲染是一項(xiàng)必做的優(yōu)化操作。
function App() {
if (true) {
return (
<>
<AdminHeader />
<Header />
<Content />
</>
)
} else {
return (
<>
<Header />
<Content />
</>
)
}
}在上面的代碼中, 當(dāng)渲染條件發(fā)生變化時(shí), React 內(nèi)部在做 Virtual DOM 比對(duì)時(shí)發(fā)現(xiàn), 剛剛第一個(gè)組件是 AdminHeader, 現(xiàn)在第一個(gè)組件是 Header, 剛剛第二個(gè)組件是 Header, 現(xiàn)在第二個(gè)組件是 Content, 組件發(fā)生了變化, React 就會(huì)卸載 AdminHeader、Header、Content, 重新掛載 Header 和 Content, 這種掛載和卸載就是沒有必要的。
function App() {
return (
<>
{true && <AdminHeader />}
<Header />
<Content />
</>
)
}避免使用內(nèi)聯(lián)樣式屬性
當(dāng)使用內(nèi)聯(lián) style 為元素添加樣式時(shí), 內(nèi)聯(lián) style 會(huì)被編譯為 JavaScript 代碼, 通過 JavaScript 代碼將樣式規(guī)則映射到元素的身上, 瀏覽器就會(huì)花費(fèi)更多的時(shí)間執(zhí)行腳本和渲染 UI, 從而增加了組件的渲染時(shí)間。
function App() {
return <div style={{ backgroundColor: "skyblue" }}>App works</div>
}避免重復(fù)無(wú)限渲染
當(dāng)應(yīng)用程序狀態(tài)發(fā)生更改時(shí), React 會(huì)調(diào)用 render 方法, 如果在 render 方法中繼續(xù)更改應(yīng)用程序狀態(tài), 就會(huì)發(fā)生 render 方法遞歸調(diào)用導(dǎo)致應(yīng)用報(bào)錯(cuò).
export default class App extends React.Component {
constructor() {
super()
this.state = {name: "張三"}
}
render() {
this.setState({name: "李四"})
return <div>{this.state.name}</div>
}
}與其他生命周期函數(shù)不同, render 方法應(yīng)該被作為純函數(shù). 這意味著, 在 render 方法中不要做以下事情, 比如不要調(diào)用 setState 方法, 不要使用其他手段查詢更改原生 DOM 元素, 以及其他更改應(yīng)用程序的任何操作. render 方法的執(zhí)行要根據(jù)狀態(tài)的改變, 這樣可以保持組件的行為和渲染方式一致.
避免數(shù)據(jù)結(jié)構(gòu)突變
組件中 props 和 state 的數(shù)據(jù)結(jié)構(gòu)應(yīng)該保持一致, 數(shù)據(jù)結(jié)構(gòu)突變會(huì)導(dǎo)致輸出不一致.
import React, { Component } from "react"
export default class App extends Component {
constructor() {
super()
this.state = {
employee: {
name: "張三",
age: 20
}
}
}
render() {
const { name, age } = this.state.employee
return (
<div>
{name}
{age}
<button
onClick={() =>
this.setState({
...this.state,
employee: {
...this.state.employee,
age: 30
}
})
}
>
change age
</button>
</div>
)
}
}到此這篇關(guān)于React 組件性能最佳優(yōu)化實(shí)踐分享的文章就介紹到這了,更多相關(guān)React 組件性能優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React父子組件傳值(組件通信)的實(shí)現(xiàn)方法
本文主要介紹了React父子組件傳值(組件通信)的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
React如何使用refresh_token實(shí)現(xiàn)無(wú)感刷新頁(yè)面
本文主要介紹了React如何使用refresh_token實(shí)現(xiàn)無(wú)感刷新頁(yè)面,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
react-router-dom入門使用教程(路由的模糊匹配與嚴(yán)格匹配)
這篇文章主要介紹了react-router-dom入門使用教程,主要介紹路由的模糊匹配與嚴(yán)格匹配,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
react中hooks使用useState的更新不觸發(fā)dom更新問題及解決
這篇文章主要介紹了react中hooks使用useState的更新不觸發(fā)dom更新問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
react?echarts?tree樹圖搜索展開功能示例詳解
這篇文章主要為大家介紹了react?echarts?tree樹圖搜索展開功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
作為老司機(jī)使用 React 總結(jié)的 11 個(gè)經(jīng)驗(yàn)教訓(xùn)
這篇文章主要介紹了作為老司機(jī)使用 React 總結(jié)的 11 個(gè)經(jīng)驗(yàn)教訓(xùn),需要的朋友可以參考下2017-04-04

