共计 7176 个字符,预计需要花费 18 分钟才能阅读完成。
React 组件性能优化最佳实际
React 组件性能优化的外围是缩小渲染实在 DOM 节点的频率,缩小 Virtual DOM 比对的频率。如果子组件未产生数据扭转不渲染子组件。
组件卸载前进行清理操作
以下代码在组件挂载时会创立一个 interval 组件销毁后革除定时器,距离 1 秒会触发渲染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 |
每次数据更新都会触发组件从新渲染,这里的优化为:组件销毁清理定时器
类组件应用纯组件PureComponent
什么是纯组件
纯组件会对组件输出数据进行浅层比拟,如果以后输出数据和上次输出数据雷同,组件不会从新渲染
什么是浅层比拟
比拟援用数据类型在内存中的援用地址是否雷同,比拟根本数据类型的值是否雷同。
为什么不间接进行 diff 操作, 而是要先进行浅层比拟,浅层比拟难道没有性能耗费吗
和进行 diff 比拟操作相比,浅层比拟将耗费更少的性能。diff 操作会从新遍历整颗 virtualDOM 树, 而浅层比拟只操作以后组件的 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 秒设置一次 name
,咱们能够看到RegularComponent
始终在渲染,即便数据没有发生变化也会渲染。PureChildComponent
只有一次渲染,因而应用纯组件会对 props
state
进行进行比拟,数据雷同不会从新渲染。
shouldComponentUpdate
纯组件只能进行浅层比拟,要进行深层比拟,应用 shouldComponentUpdate,它用于编写自定义比拟逻辑。
返回 true 从新渲染组件,返回 false 阻止从新渲染。
函数的第一个参数为 nextProps, 第二个参数为 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
的组件定时器始终批改数据也不会触发从新渲染
纯函数组件应用 React.memo
优化性能
memo 根本应用
将函数组件变为纯组件,将以后 props 和上一次的 props 进行浅层比拟,如果雷同就阻止组件从新渲染。
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 办法自定义比拟逻辑,用于执行深层比拟。)
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 |
依据条件进行组件懒加载(实用于组件不会随条件频繁切换)
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 防止额定标记
为了满足这个条件咱们通常都会在最外层增加一个 div, 然而这样的话就会多出一个无意义的标记, 如果每个组件都多出这样的一个无意义标记的话, 浏览器渲染引擎的累赘就会加剧。
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> | |
</> | |
) | |
} |
不要应用内联函数定义
在应用内联函数后, render 办法每次运行时都会创立该函数的新实例, 导致 React 在进行 Virtual DOM 比对时, 新旧函数比对不相等,导致 React 总是为元素绑定新的函数实例, 而旧的函数实例又要交给垃圾回收器解决。
谬误示范:
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})} /> | |
) | |
} | |
} |
正确的做法是在组件中独自定义函数, 将函数绑定给事件:
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} /> | |
) | |
} | |
} |
在构造函数中进行函数 this 绑定
在类组件中如果应用 fn() {} 这种形式定义函数, 函数 this 默认指向 undefined. 也就是说函数外部的 this 指向须要被更正.
能够在构造函数中对函数的 this 进行更正, 也能够在行内进行更正, 两者看起来没有太大区别, 然而对性能的影响是不同的
export default class App extends React.Component {constructor() {super() | |
// 形式一 | |
// 构造函数只执行一次, 所以函数 this 指向更正的代码也只执行一次. | |
this.handleClick = this.handleClick.bind(this) | |
} | |
handleClick() {console.log(this) | |
} | |
render() { | |
// 形式二 | |
// 问题: render 办法每次执行时都会调用 bind 办法生成新的函数实例. | |
return <button onClick={this.handleClick.bind(this)}> 按钮 </button> | |
} | |
} |
类组件中的箭头函数
在类组件中应用箭头函数不会存在 this 指向问题, 因为箭头函数自身并不绑定 this。
export default class App extends React.Component {handleClick = () => console.log(this) | |
render() {return <button onClick={this.handleClick}> 按钮 </button> | |
} | |
} |
箭头函数在 this 指向问题上占据劣势, 然而同时也有不利的一面.
当应用箭头函数时, 该函数被增加为类的实例对象属性, 而不是原型对象属性. 如果组件被屡次重用, 每个组件实例对象中都将会有一个雷同的函数实例, 升高了函数实例的可重用性造成了资源节约.
综上所述, 更正函数外部 this 指向的最佳做法仍是在构造函数中应用 bind 办法进行绑定
优化条件渲染
频繁的挂载和卸载组件是一项耗性能的操作, 为了确保应用程序的性能, 应该缩小组件挂载和卸载的次数.
在 React 中咱们常常会依据条件渲染不同的组件. 条件渲染是一项必做的优化操作。
function App() {if (true) { | |
return ( | |
<> | |
<AdminHeader /> | |
<Header /> | |
<Content /> | |
</> | |
) | |
} else { | |
return ( | |
<> | |
<Header /> | |
<Content /> | |
</> | |
) | |
} | |
} |
在下面的代码中, 当渲染条件发生变化时, React 外部在做 Virtual DOM 比对时发现, 刚刚第一个组件是 AdminHeader, 当初第一个组件是 Header, 刚刚第二个组件是 Header, 当初第二个组件是 Content, 组件产生了变动, React 就会卸载 AdminHeader、Header、Content, 从新挂载 Header 和 Content, 这种挂载和卸载就是没有必要的。
function App() { | |
return ( | |
<> | |
{true && <AdminHeader />} <Header /> | |
<Content /> | |
</> | |
) | |
} |
防止应用内联款式属性
当应用内联 style 为元素增加款式时, 内联 style 会被编译为 JavaScript 代码, 通过 JavaScript 代码将款式规定映射到元素的身上, 浏览器就会破费更多的工夫执行脚本和渲染 UI, 从而减少了组件的渲染工夫。
function App() {return <div style={{ backgroundColor: "skyblue"}}>App works</div> | |
} |
防止反复有限渲染
当应用程序状态产生更改时, React 会调用 render 办法, 如果在 render 办法中持续更改应用程序状态, 就会产生 render 办法递归调用导致利用报错.
export default class App extends React.Component {constructor() {super() | |
this.state = {name: "张三"} | |
} | |
render() {this.setState({name: "李四"}) | |
return <div>{this.state.name}</div> | |
} | |
} |
与其余生命周期函数不同, render 办法应该被作为纯函数. 这意味着, 在 render 办法中不要做以下事件, 比方不要调用 setState 办法, 不要应用其余伎俩查问更改原生 DOM 元素, 以及其余更改应用程序的任何操作. render 办法的执行要依据状态的扭转, 这样能够放弃组件的行为和渲染形式统一.
防止数据结构渐变
组件中 props 和 state 的数据结构应该保持一致, 数据结构渐变会导致输入不统一.
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> | |
) | |
} | |
} |