React性能优化SCU
生命周期方法 shouldComponentUpdate(nextProps, nextState, nextContext)
简称"SCU"
如果你定义了 shouldComponentUpdate
,React 将调用它来确定是否可以跳过重新渲染。
如果你确定你想手动编写它,你可以将 this.props
与 nextProps
以及 this.state
与 nextState
进行比较,并返回 false
来告诉 React 可以跳过更新。
jsx
import React, { Component } from 'react'
import { flushSync } from "react-dom"
function Son(props) {
return <h2>我是子组件{props.count}</h2>
}
export class App extends Component {
constructor() {
super()
this.state = { count: 0 }
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
// 进行操作比较确定是否重新渲染界面
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
add() {
this.setState({ count: this.state.count + 1 })
}
add1() {
this.setState({ count: this.state.count - 1 })
}
render() {
const { count } = this.state
console.log("render函数被执行");
return (
<div>
<h1>{"count值:" + count}</h1>
<button onClick={e => this.add()}>修改count</button>
<button onClick={e => this.add1()}>修改count1</button>
<Son count={count} />
</div>
)
}
}
export default App
当收到新的 props 或 state 时,React 会在渲染之前调用 shouldComponentUpdate
,默认值为 true
。初始渲染或使用 forceUpdate
时将不会调用此方法。
参数
nextProps
:组件即将用来渲染的下一个 props。将nextProps
与this.props
进行比较以确定发生了什么变化。nextState
:组件即将渲染的下一个 state。将nextState
与this.state
进行比较以确定发生了什么变化。nextContext
:组件将要渲染的下一个 context。将nextContext
与this.context
进行比较以确定发生了什么变化。仅当你指定了static contextType
(更新的)或static contextTypes
(旧版)时才可用。
返回值
如果你希望组件重新渲染的话就返回 true
。这是也是默认执行的操作。
返回 false
来告诉 React 可以跳过重新渲染。
注意
- 此方法 仅仅 作为性能优化而存在。如果你的组件在没有它的情况下损坏,请先修复组件。
- 可以考虑使用
PureComponent
而不是手写shouldComponentUpdate
。PureComponent
会浅比较 props 和 state 以及减少错过必要更新的概率。 - 我们不建议在
shouldComponentUpdate
中使用深度相等检查或使用JSON.stringify
。因为它使性能变得不可预测并且它与每个 prop 和 state 的数据结构有关。在最好的情况下,你可能会面临给应用程序引入多秒停顿的风险,而在最坏的情况下,你可能会面临使应用程序崩溃的风险。 - 返回
false
并不会阻止子组件在 他们的 state 发生变化时重新渲染。 - 返回
false
并不能 确保 组件不会重新渲染。React 将使用返回值作为提示,但如果是出于其他有意义的原因,它仍然可能选择重新渲染你的组件。
注意
使用 shouldComponentUpdate
来优化类式组件与使用 memo
来优化函数式组件类似。函数式组件还使用 useMemo
来提供更精细的优化。
PureComponent★★★
PureComponent
是 Component
的子类,并且支持 所有 Component
的 API。继承 PureComponent
的子类相当与定义了一个自定义的 shouldComponentUpdate
方法,该方法将浅比较 props 和 state。
跳过类式组件不必要的重新渲染
当父组件重新渲染时,React 通常会重新渲染子组件。为了优化性能,你可以创建一个组件,在父组件重新渲染时不会重新渲染,前提是新的 props 和 state 与旧的 props 和 state 相同。类式组件可以通过继承 PureComponent
来选择此行为
- 如果你的组件正在使用的 context 发生变化,它仍会重新渲染。
jsx
import React, { PureComponent } from 'react'
//子组件
class Son extends PureComponent {
render() {
console.log("我是子组件观察时候执行更新");
const { message } = this.props
return (
<>
<h1>{message}</h1>
</>
)
}
}
export class App extends PureComponent {
constructor() {
super()
this.state = {
count: 0,
message: "林夕"
}
}
add() {
this.setState({ count: this.state.count + 1 })
}
changeText() {
this.setState({ message: "修改后的林夕" })
}
render() {
const { count, message } = this.state
console.log("render函数被执行");
return (
<div>
<h1>{"count值:" + count}</h1>
<h1>{"message值:" + message}</h1>
<button onClick={e => this.add()}>修改count</button>
<button onClick={e => this.changeText()}>修改message</button>
<Son message={message}></Son>
</div>
)
}
}
export default App
但是在函数组件里面无论我们是否修改每次父组件渲染都会渲染一次子组件
我们可以,将其包装在 memo
:
jsx
import React, { PureComponent, memo } from 'react'
const Son = memo(function Son(props) {
console.log("修改count,函数组件是否执行");
return <h2>我是子组件{props.message}</h2>
})
export class App extends PureComponent {
constructor() {
super()
this.state = {
count: 0,
message: "林夕"
}
}
add() {
this.setState({ count: this.state.count + 1 })
}
changeText() {
this.setState({ message: "修改后的林夕" })
}
render() {
const { count, message } = this.state
console.log("render函数被执行");
return (
<div>
<h1>{"count值:" + count}</h1>
<h1>{"message值:" + message}</h1>
<button onClick={e => this.add()}>修改count</button>
<button onClick={e => this.changeText()}>修改message</button>
<Son message={message}></Son>
</div>
)
}
}
export default App
注意:
与 PureComponent
不同,memo
不会比较新旧 state。在函数组件中,即使没有 memo
,调用具有相同 state 的 set
函数,默认已经阻止了重新渲染。
不可变数据的力量★★★
避免更改你正用于 props 或 state 的值,如果要修改需要拿到构造函数中定义的原数据进行操作
如果需要修改深层数据建议使用如下方式
jsx
//...
constructor() {
super()
this.state = {
arrayList: [
{ name: "林夕", age: 18, count: 1 },
{ name: "林夕1", age: 18, count: 2 },
{ name: "林夕2", age: 18, count: 3 },
]
}
}
addCount(index) {
let arrayList = [...this.state.arrayList];
arrayList[index].count++
this.setState({arrayList})
}
//...
ES6 数组支持扩展运算符
ini
handleClick() {
this.setState(state => ({
words: [...state.words, 'marklar'],
}));
};
你可以用类似的方式改写代码来避免可变对象的产生。例如,我们有一个叫做 colormap
的对象。我们希望写一个方法来将 colormap.right
设置为 'blue'
。我们可以这么写:
ini
function updateColorMap(colormap) {
colormap.right = 'blue';
}
为了不改变原本的对象,我们可以使用 Object.assign 方法:
javascript
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
现在 updateColorMap
返回了一个新的对象,而不是修改老对象。Object.assign
是 ES6 的方法,需要 polyfill。
这里有一个 JavaScript 的提案,旨在添加对象扩展属性以使得更新不可变对象变得更方便:
javascript
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}