React 组件性能优化最佳实践
React 组件性能优化的核心是减少渲染真实 DOM 节点的频率,减少 Virtual DOM 比对的频率。
1. 组件卸载前进行清理操作
在组件中为 window 注册的全局事件, 以及定时器, 在组件卸载前要清理掉, 防止组件卸载后继续执行影响应用性能.
需求:开启定时器然后卸载组件,查看组件中的定时器是否还在运行。
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
2. PureComponent
-
什么是纯组件
纯组件会对组件输入数据进行浅层比较,如果当前输入数据和上次输入数据相同,组件不会重新渲染。
-
什么是浅层比较
比较引用数据类型在内存中的引用地址是否相同,比较基本数据类型的值是否相同。
-
如何实现纯组件
类组件继承 PureComponent 类,函数组件使用 memo 方法
-
为什么不直接进行 diff 操作, 而是要先进行浅层比较,浅层比较难道没有性能消耗吗
和进行 diff 比较操作相比,浅层比较将消耗更少的性能。diff 操作会重新遍历整颗 virtualDOM 树, 而浅层比较只操作当前组件的 state 和 props。
-
需求:在状态对象中存储 name 值为张三,组件挂载完成后将 name 属性的值再次更改为张三,然后分别将 name 传递给纯组件和非纯组件,查看结果。
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> } }
3. 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>
}
}
4. React.memo
-
memo 基本使用
将函数组件变为纯组件,将当前 props 和上一次的 props 进行浅层比较,如果相同就阻止组件重新渲染。
需求:父组件维护两个状态,index 和 name,开启定时器让 index 不断发生变化,name 传递给子组件,查看父组件更新子组件是否也更新了。
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(() =&g