携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情
React 组件的性能优化
在 React 中如何对组件的性能进行优化,主要有以下几点:
- 减轻 state 存储
- 避免不必要的重新渲染
- 使用纯组件
1. 减轻 state 存储
如何减轻 state 存储来优化组件的性能呢?其实只需要在存储时只存储跟组件渲染相关的数据即可,比如页面需要展示的列表数据,loading 效果等等;而那些不用做渲染的数据就不要放在 state 中了,比如定时器等等;而对于这种需要在多个方法中用到的数据,可以放在 this 中。
如以下代码中 num 就是需要在页面上渲染的数据,可放在** state** 中;而定时器 timer 不需要在页面中渲染,但是又需要在 componentDidMount 和 componentWillUnmount 中用到,所以放在 **this **中:
// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'
// App组件
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0
};
}
handleClick = () => {
this.setState(
state => ({ num: state.num + 1 })
)
}
render() {
return (
<div className='app' >
{this.state.num > 5 ? <p>最大数值只能是5!</p> : <Child num={this.state.num} />}
<button onClick={this.handleClick}>num + 1</button>
</div>
);
}
}
// 子组件
class Child extends React.Component {
componentDidMount() {
this.timer = setInterval(() => {
console.log('定时器正在执行~');
}, 2000)
}
componentWillUnmount() {
console.log("子组件即将卸载~");
clearInterval(this.timer);
console.log("清除定时器~");
}
render() {
return (
<p>次数统计:{this.props.num}</p>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
2. 避免不必要的重新渲染
之前我有说到 React 组件的更新机制就是如果某个组件重新渲染,那么其下面的子组件树也会跟着重新渲染,这也就说明即使子组件中没有任何变化,那么也会渲染,那这种就是属于不必要的渲染,也会影响组件的性能。对于这种情况可以使用钩子函数 shouldComponentUpdate 来解决,通过该钩子函数的返回值(true 或 false)来决定是否重新渲染组件。
代码如下:
// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'
// App组件
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0
};
}
// 钩子函数
shouldComponentUpdate(nextProps, nextState) {
// 获取最新的状态
console.log('父组件App最新的状态:', nextState)
// 获取更新前的状态
console.log('父组件App更新前的状态:', this.state)
// 该返回值决定是否渲染该组件(true:重新渲染,false:不重新渲染)
if (nextState.num !== this.state.num) {
return true
} else {
return false
}
}
handleClick = () => {
this.setState(() => ({
num: Math.floor(Math.random() * 3)
})
)
}
render() {
console.log('父组件App更新了!')
return (
<div className='app' >
<p>父组件次数统计:{this.state.num}</p>
<Child num={this.state.num} />
<button onClick={this.handleClick}>num + 1</button>
</div>
);
}
}
// 子组件
class Child extends React.Component {
// 钩子函数
shouldComponentUpdate(nextProps, nextState) {
// 获取最新的状态
console.log('子组件Child最新的props:', nextProps)
// 获取更新前的状态
console.log('子组件Child更新前的props:', this.props)
// 该返回值决定是否渲染该组件(true:重新渲染,false:不重新渲染)
if (nextProps.num !== this.props.num) {
return true
} else {
return false
}
}
render() {
console.log('子组件Child更新了!')
return (
<p>子组件次数统计:{this.props.num}</p>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
演示如下:
- 当父组件状态发生改变时,父组件的 shouldComponentUpdate 钩子函数将返回 true,页面将重新渲染;
- 而由于子组件接收的是父组件的状态,所以子组件的 shouldComponentUpdate 钩子函数返回的也是 true,页面也重新渲染了;
- 如果父组件状态不变,则父组件的 shouldComponentUpdate 钩子函数将返回 false,页面不会重新渲染,那么子组件也不会重新渲染。
3. 使用纯组件来进行性能优化
纯组件就是 React.PureComponent,其功能与 React.Component 功能相似,其优点是自动实现 shouldComponentUpdate 钩子函数,纯组件内部可以通过分别对比 props 和 state 的值来决定是否重新渲染组件,不需要再手动比较了。
代码如下:
// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'
// App组件
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
num: 0
};
}
handleClick = () => {
this.setState(() => ({
num: Math.floor(Math.random() * 3)
})
)
}
render() {
console.log('父组件App更新了!')
return (
<div className='app' >
<p>父组件中的随机数为:{this.state.num}</p>
<Child num={this.state.num} />
<button onClick={this.handleClick}>生成随机数</button>
</div>
);
}
}
// 子组件
class Child extends React.PureComponent {
render() {
console.log('子组件Child更新了!')
return (
<p>子组件中的随机数为:{this.props.num}</p>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
演示如下:
与方法2中效果一致,而且不用写 shouldComponentUpdate 钩子函数,代码更简洁。
但是使用纯组件时会出现一个问题,那就是由于纯组件的内部对比是一个浅层对比,所以如果只是比较值类型的值,那么没有问题,而如果比较的是引用类型的值,那么就会有问题,因为引用类型比较的是对象的地址是否相同。
// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'
// App组件
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
people: {
age: 20
}
};
}
handleClick = () => {
const newPeople = this.state.people
newPeople.age = Math.floor(Math.random() * 30)
this.setState(() => ({
people: newPeople
})
)
}
render() {
console.log('父组件App更新了!')
return (
<div className='app' >
<p>年龄:{this.state.people.age}</p>
<button onClick={this.handleClick}>生成随机的年龄</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'))
解决这个问题的办法就是我们在使用纯组件对比引用类型的值时,应该创建新的数据,不要直接修改原数据。
// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'
// App组件
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
people: {
age: 20
}
};
}
handleClick = () => {
// 创建新的对象
const newPeople = { ...this.state.people, age: Math.floor(Math.random() * 30) }
// 或深拷贝
// const newPeople = JSON.parse(JSON.stringify(this.state.people))
// newPeople.age = Math.floor(Math.random() * 30)
this.setState(() => ({
people: newPeople
})
)
}
render() {
console.log('父组件App更新了!')
return (
<div className='app' >
<p>年龄:{this.state.people.age}</p>
<button onClick={this.handleClick}>生成随机的年龄</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'))
对于数组,不要用 push/unshift 等直接修改当前数组的方法,而应该用 concat/slice等返回新数组的方法,代码如下:
// 导入ract
import React from 'react'
import ReactDOM from 'react-dom'
// App组件
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
numList: [0, 1, 2, 3, 4, 5]
};
}
handleClick = () => {
const newNumList = this.state.numList.concat([6, 7, 8, 9, 10])
this.setState(() => ({
numList: newNumList
})
)
}
render() {
console.log('父组件App更新了!')
return (
<div className='app' >
<p>当前数组:{this.state.numList}</p>
<button onClick={this.handleClick}>改变数组</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'))