React?組件性能最佳優(yōu)化實踐分享
React 組件性能優(yōu)化最佳實踐
React 組件性能優(yōu)化的核心是減少渲染真實 DOM 節(jié)點(diǎn)的頻率,減少 Virtual DOM 比對的頻率。如果子組件未發(fā)生數(shù)據(jù)改變不渲染子組件。
組件卸載前進(jìn)行清理操作
以下代碼在組件掛載時會創(chuàng)建一個interval組件銷毀后清除定時器,間隔1秒會觸發(fā)渲染count+1
,組件銷毀后如果不清除定時器它會一直消耗資源
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ù)更新都會觸發(fā)組件重新渲染,這里的優(yōu)化為:組件銷毀清理定時器
類組件使用純組件PureComponent
什么是純組件
純組件會對組件輸入數(shù)據(jù)進(jìn)行淺層比較,如果當(dāng)前輸入數(shù)據(jù)和上次輸入數(shù)據(jù)相同,組件不會重新渲染
什么是淺層比較
比較引用數(shù)據(jù)類型在內(nèi)存中的引用地址是否相同,比較基本數(shù)據(jù)類型的值是否相同。
為什么不直接進(jìn)行 diff 操作, 而是要先進(jìn)行淺層比較,淺層比較難道沒有性能消耗嗎
和進(jìn)行 diff 比較操作相比,淺層比較將消耗更少的性能。diff 操作會重新遍歷整顆 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> } }
組件掛載以后會有一個定時器間隔1秒設(shè)置一次name
,我們可以看到RegularComponent
一直在渲染,即使數(shù)據(jù)沒有發(fā)生變化也會渲染。PureChildComponent
只有一次渲染,因此使用純組件會對props
state
進(jìn)行進(jìn)行比較,數(shù)據(jù)相同不會重新渲染。
shouldComponentUpdate
純組件只能進(jìn)行淺層比較,要進(jìn)行深層比較,使用 shouldComponentUpdate,它用于編寫自定義比較邏輯。
返回 true 重新渲染組件,返回 false 阻止重新渲染。
函數(shù)的第一個參數(shù)為 nextProps, 第二個參數(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ù)據(jù)也不會觸發(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 App
memo 傳遞比較邏輯
(使用 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)行組件懶加載(適用于組件不會隨條件頻繁切換)
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)記
為了滿足這個條件我們通常都會在最外層添加一個div, 但是這樣的話就會多出一個無意義的標(biāo)記, 如果每個組件都多出這樣的一個無意義標(biāo)記的話, 瀏覽器渲染引擎的負(fù)擔(dān)就會加劇。
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)行時都會創(chuàng)建該函數(shù)的新實例, 導(dǎo)致 React 在進(jìn)行 Virtual DOM 比對時, 新舊函數(shù)比對不相等,導(dǎo)致 React 總是為元素綁定新的函數(shù)實例, 而舊的函數(shù)實例又要交給垃圾回收器處理。
錯誤示范:
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ù)中對函數(shù)的 this 進(jìn)行更正, 也可以在行內(nèi)進(jìn)行更正, 兩者看起來沒有太大區(qū)別, 但是對性能的影響是不同的
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í)行時都會調(diào)用 bind 方法生成新的函數(shù)實例. return <button onClick={this.handleClick.bind(this)}>按鈕</button> } }
類組件中的箭頭函數(shù)
在類組件中使用箭頭函數(shù)不會存在 this 指向問題, 因為箭頭函數(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)勢, 但是同時也有不利的一面.
當(dāng)使用箭頭函數(shù)時, 該函數(shù)被添加為類的實例對象屬性, 而不是原型對象屬性. 如果組件被多次重用, 每個組件實例對象中都將會有一個相同的函數(shù)實例, 降低了函數(shù)實例的可重用性造成了資源浪費(fèi).
綜上所述, 更正函數(shù)內(nèi)部 this 指向的最佳做法仍是在構(gòu)造函數(shù)中使用 bind 方法進(jìn)行綁定
優(yōu)化條件渲染
頻繁的掛載和卸載組件是一項耗性能的操作, 為了確保應(yīng)用程序的性能, 應(yīng)該減少組件掛載和卸載的次數(shù).
在 React 中我們經(jīng)常會根據(jù)條件渲染不同的組件. 條件渲染是一項必做的優(yōu)化操作。
function App() { if (true) { return ( <> <AdminHeader /> <Header /> <Content /> </> ) } else { return ( <> <Header /> <Content /> </> ) } }
在上面的代碼中, 當(dāng)渲染條件發(fā)生變化時, React 內(nèi)部在做 Virtual DOM 比對時發(fā)現(xiàn), 剛剛第一個組件是 AdminHeader, 現(xiàn)在第一個組件是 Header, 剛剛第二個組件是 Header, 現(xiàn)在第二個組件是 Content, 組件發(fā)生了變化, React 就會卸載 AdminHeader、Header、Content, 重新掛載 Header 和 Content, 這種掛載和卸載就是沒有必要的。
function App() { return ( <> {true && <AdminHeader />} <Header /> <Content /> </> ) }
避免使用內(nèi)聯(lián)樣式屬性
當(dāng)使用內(nèi)聯(lián) style 為元素添加樣式時, 內(nèi)聯(lián) style 會被編譯為 JavaScript 代碼, 通過 JavaScript 代碼將樣式規(guī)則映射到元素的身上, 瀏覽器就會花費(fèi)更多的時間執(zhí)行腳本和渲染 UI, 從而增加了組件的渲染時間。
function App() { return <div style={{ backgroundColor: "skyblue" }}>App works</div> }
避免重復(fù)無限渲染
當(dāng)應(yīng)用程序狀態(tài)發(fā)生更改時, React 會調(diào)用 render 方法, 如果在 render 方法中繼續(xù)更改應(yīng)用程序狀態(tài), 就會發(fā)生 render 方法遞歸調(diào)用導(dǎo)致應(yīng)用報錯.
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)突變會導(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)化實踐分享的文章就介紹到這了,更多相關(guān)React 組件性能優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React如何使用refresh_token實現(xiàn)無感刷新頁面
本文主要介紹了React如何使用refresh_token實現(xiàn)無感刷新頁面,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04react-router-dom入門使用教程(路由的模糊匹配與嚴(yán)格匹配)
這篇文章主要介紹了react-router-dom入門使用教程,主要介紹路由的模糊匹配與嚴(yán)格匹配,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08react中hooks使用useState的更新不觸發(fā)dom更新問題及解決
這篇文章主要介紹了react中hooks使用useState的更新不觸發(fā)dom更新問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01react?echarts?tree樹圖搜索展開功能示例詳解
這篇文章主要為大家介紹了react?echarts?tree樹圖搜索展開功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01作為老司機(jī)使用 React 總結(jié)的 11 個經(jīng)驗教訓(xùn)
這篇文章主要介紹了作為老司機(jī)使用 React 總結(jié)的 11 個經(jīng)驗教訓(xùn),需要的朋友可以參考下2017-04-04