react性能优化
react以组件的形式来组织逻辑,组件允许我们将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。因此 React 有一些充满了自身特色的性能优化思路,这些思路基本都围绕“组件性能优化”这个中心思想展开:
这里主要通过一下三个方面进行优化
- 使用 shouldComponentUpdate 规避重复渲染
- 使用 PureComponent
- 使用 React.memo 与 useMemo 缓存组件
shouldComponentUpdate
shouldComponentUpdate 是 React 类组件的一个生命周期。
shouldComponentUpdate里判断props或state的数据是否发生变化,通过返回ture(更新)和false(不更新)来阻止不必要的render.
shouldComponentUpdate的调用方式入下
shouldComponentUpdate(nextProps, nextState)
在 React 当中,很多时候我们会不经意间就频繁地调用了 render。为了避免不必要的 render 操作带来的性能开销,React 提供了 shouldComponentUpdate 这个周期,我们可以在这个钩子中进行提前预判,判断是否需要执行后面的周期函数。
为了更直观的看到这个情况,我们写一个demo来看下具体情况
这里我们写两个组件一个父组件:
childA.js
import React from "react";
export default class ChildA extends React.Component {
render () {
console.log("ChildA 的render方法执行了");
return (
<div>
子组件A的内容:
{this.props.text}
</div>
);
}
}
这里childB.js和childA相似:展示文本不一样,然后将childA和childB放入父组件app.js中:
import React from "react";
import ChildA from './ChildA'
import ChildB from './ChildB'
class App extends React.Component {
state = {
textA: '我是A的文本',
textB: '我是B的文本'
}
changeA = () => {
this.setState({
textA: 'A的文本被修改了'
})
}
changeB = () => {
this.setState({
textB: 'B的文本被修改了'
})
}
render() {
return (
<div className="App">
<div className="container">
<div className='buttons' onClick={this.changeA}>点击修改A处的文本</div>
<div className='buttons' onClick={this.changeB}>点击修改B处的文本</div>
<ul>
<li>
<ChildA text={this.state.textA}/>
</li>
<li>
<ChildB text={this.state.textB}/>
</li>
</ul>
</div>
</div>
);
}
}
export default App;
运行代码,我们发现a组将和b组件都进行了render渲染,结果如下:
紧接着,我们点击buttons修改子组件中props的文字,结果如下:
我们会发现,点击第一个按钮,修改a组件props的文字,b组件会重新渲染,修改b组件props的文字,a组件会重新渲染,
在 React 中,只要父组件发生了更新,那么所有的子组件都会被无条件更新。这就导致了 b组件 的 props 尽管没有发生任何变化,它本身也没有任何需要被更新的点,却还是会走一遍更新流程。
同样的情况也适用于组件自身的更新:当组件自身调用了 setState 后,那么不管 setState 前后的状态内容是否真正发生了变化,都会重新渲染。
设置shouldComponentUpdate来进行数据判断
shouldComponentUpdate (newProps, newStates) {
// 判断 text 属性在父组件更新前后有没有发生变化,若没有发生变化,则返回 false
if (this.props.text === newProps.text) {
return false
}
//若不相同,则走一遍流程
return true
}
当父组件 App 组件发生更新、进而触发 childB 的更新流程时,shouldComponentUpdate 就相当于一个组件守卫:它会检查新更新的props.text是否和之前的值一致,如果一致,那么就不会执行周期函数,直接返回“false”将整个 childB 的生命周期中断掉即可。只有当 props.text 发生变化时,它才会继续执行生命周期,重新渲染组件。
设置了 shouldComponentUpdate 后,再次点击更新按钮,修改 childA 的渲染内容时,我们看看渲染结果是怎么样的:
这里我们点击修改A处的按钮,点击后发现childA的组件进行了修改,childB并没有进行重新渲染,避免了不必要的渲染,使用 shouldComponentUpdate 来比较从而进行不必要的更新,避免无意义的渲染,这是 React 组件中最基本的性能优化手段,也是最重要的手段。
PureComponent
shouldComponentUpdate 虽然帮我们解决了性能方面的问题,但每次比较,都要手动实现一次 shouldComponentUpdate,说实话有点太麻烦了。
我们可以将其封装成一个组件,这样可以大大的避免多次去写shouldComponentUpdate
React有一个 PureComponent 的类,解决了多次写 shouldComponentUpdate 的问题。
我们使用PureComponent来试试
export default class ChildA extends React.PureComponent {
render () {
console.log("ChildB 的render方法执行了");
return (
<div>
子组件B的内容:
{this.props.text}
</div>
);
}
}
然后点击修改按钮A,预期效果很满意,和shouldComponentUpdate实现效果差不多,
但是很快就会发现问题,PureComponent只会对值应用发生作用,当对象应用不会发生作用,还会重新渲染
changeA = () => {
console.log('xhiiang');
let { personA } = this.state
personA.name = 'ZLP'
this.setState({
personA,
})
}
export default class ChildA extends React.PureComponent {
render () {
console.log("ChildB 的render方法执行了");
return (
<div>
子组件B的内容:
{this.props.text}
{this.props.personA.name}
</div>
);
}
}
PureComponent 与 Component 的就在于:PureComponent 将会在 shouldComponentUpdate 中对组件更新前后的 props 和 state 进行浅比较,并根据浅比较的结果,决定是否需要继续更新流程。
所以当点击修改数据时会发现,组件并不会重新渲染,因为它们进行浅比较,引用相同
React.memo和userMemo
React.memo 是 React 导出的一个顶层函数,它本质上是一个高阶组件,负责对函数组件进行包装。
import React from "react";
function childA (props) {
console.log("ChildA 的render方法执行了");
return (
<div>
子组件A的内容:
{props.text}
</div>
);
}
// areEqual 用于对比 props 的变化
function areEqual (prevProps, nextProps) {
if (prevProps.text === nextProps.text) {
return true
}
return false
}
export default React.memo(childA, areEqual)
执行结果如下:
同样的也是,修改组件a,b的数组没有改变,不会重现渲染
React.memo 接收两个参数,第一个参数是我们需要渲染的组件,第二个参数 用来进行 props 的对比逻辑。之前我们在 shouldComponentUpdate 里面做的事情,现在就可以放在 第二个参数 里来做
当然你可以不写第二个参数,但是他会根据你组件的props进行浅比较
useMemo
React.memo 控制是否需要重渲染一个组件,而 useMemo 控制的则是是否需要重复执行某一段逻辑。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
我们可以把目标逻辑作为第一个参数传入,把逻辑的依赖项数组作为第二个参数传入。这样只有当依赖项数组中的某个依赖发生变化时,useMemo 才会重新执行第一个入参中的目标逻辑。
import React, { useMemo } from "react";
export default function ChildB ({ text, count }) {
console.log("ChildB 组件render 执行");
// text 文本的渲染逻辑
const renderText = (text) => {
console.log('renderText 执行了')
return <p>
子组件B的文本内容:
{text}
</p>
}
// count 数字的渲染逻辑
const renderCount = (count) => {
console.log('renderCount 执行了')
return <p>
子组件B的数字内容:
{count}
</p>
}
// 使用 useMemo 加持两段渲染逻辑
const textContent = useMemo(() => renderText(text), [text])
const countContent = useMemo(() => renderCount(count), [count])
return (
<div className="childB">
{textContent}
{countContent}
</div>
);
}
// app.js修改数据
state = {
textA: '我是A',
stateB: {
text: '我是B的文本',
count: 10
}
}
changeA = () => {
this.setState({
textA:'修改后的A'
})
}
changeB = () => {
this.setState({
stateB: {
...this.state.stateB,
count: 100
}
})
}
我们使用useMome后,看看执行结果:
首次渲染,textContent和countContent都执行了,
然后我们点击修改a组件的文字,操作结果如下:
会发现细化的textContent和countContent并没有重新渲染,组件b渲染了
然后我们在点击修改b的count,执行结果如下:
这次会发现,组件b重新渲染,countContent重新渲染,由于text没有变化,所以textContent也没有重新渲染