react 性能优化
React 组件性能优化的核心就是减少渲染真实DOM节点的频率,减少Virtual DOM 对比的频率,以此来提高性能
1. 组件卸载之前进行清理操作
在组件中为window 注册的全局事件,以及定时器,在组件卸载前要清理掉,防止组件卸载后继续执行影响应用性能
我们开启一个定时器然后卸载组件,查看组件中的定时器是否还在运行 Test 组件来开启一个定时器
import {useEffect} from 'react'
export default function Test () {
useEffect(() => {
setInterval(() => {
console.log('定时器开始执行')
}, 1000)
}, [])
return Test
}
在App.js中引入定时器组件然后用flag变量来控制渲染和卸载组件
import Test from "./Test";
import { useState } from "react"
function App() {
const [flag, setFlag] = useState(true)
return (
{ flag && }
);
}
export default App;
在浏览器中我们去点击按钮发现组件被卸载后定时器还在执行,这样组件太多之后或者这个组件不停的渲染和卸载会开启很多的定时器,我们应用的性能肯定会被拉垮,所以我们需要在组建卸载的时候去销毁定时器。
import {useEffect} from 'react'
export default function Test () {
useEffect(() => {
// 因为要销毁定时器所以我们需要用一个变量来接受定时器id
const InterValTemp = setInterval(() => {
console.log('定时器开始执行')
}, 1000)
return () => {
console.log(`ID为${InterValTemp}定时器被销毁了`)
clearInterval(InterValTemp)
}
}, [])
return Test
}
这个时候我们在去点击销毁组建的时候定时器就被销毁掉了
2. 类组件用纯组件来提升组建性能PureComponent
1. 什么是纯组件
纯组件会对组建的输入数据进行浅层比较,如果输入数据和上次输入数据相同,组建不会被重新渲染
2. 什么是浅层比较
比较引用数据类型在内存中的引用地址是否相同,比较基本数据类型的值是否相同
3. 如何实现纯组件
类组件集成 PureComponent 类,函数组件使用memo方法
4. 为什么不直接进行diff操作,而是要进行浅层比较,浅层比较难到没有性能消耗吗
和进行 diff 比较操作相比,浅层比较小号更少的性能,diff 操作会重新遍历整个 virtualDOM 树,而浅层比较只比较操作当前组件的 state和props
在状态中存储一个name为张三的,在组建挂载后我们每隔1秒更改name的值为张三,然后我们看纯组件和非纯组件,查看结果
// 纯组件
import { PureComponent } from 'react'
class PureComponentDemo extends PureComponent {
render () {
console.log("纯组件")
return {this.props.name}
}
}
// 非纯组件
import { Component } from 'react'
class ReguarComponent extends Component {
render () {
console.log("非纯组件")
return {this.props.name}
}
}
引入纯组件和非纯组件 并在组件挂在后开启定时器每隔1秒更改name的值为张三
import { Component } from 'react'
import { ReguarComponent, PureComponentDemo } from './PureComponent'
class App extends Component {
constructor () {
super()
this.state = {
name: '张三'
}
}
updateName () {
setInterval(() => {
this.setState({name: "张三"})
}, 1000)
}
componentDidMount () {
this.updateName()
}
render () {
return
}
}
打开浏览器查看执行结果
我们发现纯组件只执行了一次,以后在改相同的值的时候,并没有再重新渲染组件,而非纯组件则是每次更改都在重新渲染,所以纯组件要比非纯组件更节约性能
3. 函数组件来实现纯组件 memo
-
memo 基本使用
将函数组件变成纯组件,将当前的props和上一次的props进行浅层比较,如果相同就组件组件的渲染。》。
我们在父组件中维护两个状态,index和name 开启定时器让index不断地发生变化,name传递给子组件,查看父组件更新子组件是否也更新了, 我们先不用memo来查看结果
import { useState, useEffect } from 'react'
function App () {
const [ name ] = useState("张三")
const [index, setIndex] = useState(0)
useEffect(() => {
setInterval (() => {
setIndex(prev => prev + 1)
}, 1000)
}, [])
return
{index}
}
function ShowName ({name}) {
console.log("组件被更新")
return {name}
}
打开浏览器查看执行结果
在不使用 memo 来把函数组件变成纯组件的情况下我们发现子组件随着父组件更新而一起重新渲染,但是它依赖的值并没有更新,这样浪费了性能,我们使用 memo 来避免没必要的更新
import { useState, useEffect, memo } from 'react'
const ShowName = memo(function ShowName ({name}) {
console.log("组件被更新")
return {name}
})
function App () {
const [ name ] = useState("张三")
const [index, setIndex] = useState(0)
useEffect(() => {
setInterval (() => {
setIndex(prev => prev + 1)
}, 1000)
}, [])
return
{index}
}
我们再次打开浏览器查看执行结果
现在index变动 子组件没有重新渲染了,用 memo 把组件变为纯组件之后就避免了依赖的值没有更新却跟着父组件一起更新的情况
4. 函数组件来实现纯组件(为memo方法传递自定义比较逻辑)
memo 方法也是浅层比较
memo 方法是有第二个参数的第二个参数是一个函数
这个函数有个两个参数,第一个参数是上一次的props,第二个参数是下一个props
这个函数返回 false 代表重新渲染, 返回true 重新渲染
比如我们有员工姓名和职位两个数据,但是页面中只使用了员工姓名,那我们只需要观察员工姓名发生变动没有,所以我们在memo的第二个参数去比较是否需要重新渲染
import { useState, useEffect, memo } from 'react'
function compare (prevProps, nextProps) {
if (prevProps.person.name !== nextProps.person.name) {
return false
}
return true
}
const ShowName = memo(function ShowName ({person}) {
console.log("组件被更新")
return {person.name}
}, compare)
function App () {
const [ person, setPerson ] = useState({ name: "张三", job: "工程师"})
useEffect(() => {
setInterval (() => {
setPerson({
...person,
job: "挑粪"
})
}, 1000)
}, [person])
return
}
5. shouldComponentUpdata
纯组件只能进行浅层比较,要进行深层次比较,使用 shouldComponentUpdate,它用于编写自定义比较逻辑
返回true 重新渲染组件, 返回 false 组件重新渲染组件
函数的第一个参数为 nextProps,第二个参数为NextState
比如我们有员工姓名和职位两个数据,但是页面中只使用了员工姓名,那我们只需要观察员工姓名发生变动没有,利用shouldComponentUpdata来控制只有员工姓名发生变动才重新渲染组件,我们查看使用 shouldComponentUpdata 生命周期函数和不使用shouldComponentUpdata生命周期函数的区别
// 没有使用的组件
import { Component } from 'react'
class App extends Component {
constructor () {
super()
this.state = {
person: {
name: '张三',
job: '工程师'
}
}
}
componentDidMount (){
setTimeout (() => {
this.setState({
person: {
...this.state.person,
job: "修水管"
}
})
}, 2000)
}
render () {
console.log("render 方法执行了")
return
{this.state.person.name}
}
}
我们打开浏览器等待两秒
发现render方法执行了两次,组件被重新渲染了,但是我们并没有更改name 属性,所以这样浪费了性能,我们用shouldComponentUpdata生命周期函数来判断name是否发生了改变
import { Component } from 'react'
class App extends Component {
constructor () {
super()
this.state = {
person: {
name: '张三',
job: '工程师'
}
}
}
componentDidMount (){
setTimeout (() => {
this.setState({
person: {
...this.state.person,
job: "修水管"
}
})
}, 2000)
}
render () {
console.log("render 方法执行了")
return
{this.state.person.name}
}
shouldComponentUpdate (nextProps, nextState) {
if (this.state.person.name !== nextState.person.name) {
return true;
}
return false;
}
}
我们再打开浏览器等待两秒之后
我们只改变了job 的时候render方法只执行了一次,这样就减少了没有必要的渲染,从而节约了性能
6. 使用组件懒加载
使用路由懒加载可以减少bundle文件大小,从而加快组建呈递速度
创建 Home 组建
// Home.js
function Home() {
return (
首页
)
}
export default Home
创建 List 组建
// List.js
function List() {
return (
列表页
)
}
export default List
从react-router-dom包中引入 BrowserRouter, Route, Switch, Link 和 home 与list 来创建路由规则以及切换区域和跳转按钮
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom'
import Home from './Home';
import List from './List';
function App () {
return
首页
列表页
}
使用 lazy, Suspense 来创建加载区域与加载函数
import { lazy, Suspense } from 'react';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom'
const Home = lazy(() => import('./Home'))
const List = lazy(() => import('./List'))
function Loading () {
return loading
}
function App () {
return
首页
列表页
}>
}
使用注解方式来为打包后的文件命名
const Home = lazy(() => import(/* webpackChunkName: "Home" */'./Home'))
const List = lazy(() => import(/* webpackChunkName: "List" */'./List'))
7. 根据条件进行组件懒加载
适用于组件不会随条件频繁切换
import { lazy, Suspense } from 'react';
function App () {
let LazyComponent = null;
if (false){
LazyComponent = lazy(() => import(/* webpackChunkName: "Home" */'./Home'))
} else {
LazyComponent = lazy(() => import(/* webpackChunkName: "List" */'./List'))
}
return
loading }>