一文讲清楚React的render优化,包括shouldComponentUpdate、PureComponent和memo

文章目录

  • 一文讲清楚React的render优化,包括shouldComponentUpdate、PureComponent和memo
  • [1. React的渲染render机制](#1. React的渲染render机制)
  • [2. shouldComponentUpdate](#2. shouldComponentUpdate)
    • [2.1 先上单组件渲染,验证state变化](#2.1 先上单组件渲染,验证state变化)
    • [2.2 上父子组件,验证props](#2.2 上父子组件,验证props)
  • [2. PureComponent](#2. PureComponent)
    • [2.1 单组件验证state](#2.1 单组件验证state)
    • [2.2 父子组件验证props](#2.2 父子组件验证props)
  • 3.React.memo
  • [4. 总结](#4. 总结)

一文讲清楚React的render优化,包括shouldComponentUpdate、PureComponent和memo

1. React的渲染render机制

  • 我们都知道,在React中,state和props的改变以及父组件人render执行都会造成render的重新执行
  • 关于这点不懂的,看这篇文章一文讲清楚React中的render机制
  • 但是在某些复杂的业务场景下,如果大量的子组件在没有发生任何变化的情况下被更新,就会造成一定的性能影响
  • 我们希望的是,只有在子组件state和props有变化,且判定为需要重新渲染的情况下再执行render进行渲染
  • 能不能做到呢,恭喜,还真能做到
  • 我们可以通过三驾马车,shouldComponentUpdate、PureComponent、memo来实现

2. shouldComponentUpdate

  • 见名知意,组件应不应该更新,所以这里应该有一个条件判断,如果返回true,则更新,如果返回false,则不更新
  • 判断什么呢,判断state和props是否发生变化,如果变化了,说明页面需要重新渲染,那我们就是返回true,反之亦然
  • 话不多说,上代码

2.1 先上单组件渲染,验证state变化

js 复制代码
import React from 'react'
class App extends React.Component{
    constructor(props){
        super(props)
        this.state={
            num:0
        }
    }
    handleClick=()=>{
        this.setState({
            num:0
        })
    }
    render(){
        console.log('render() has been executed')
        return(
            <div>
                <div>{this.state.num}</div>
                <button onClick={this.handleClick}>button</button>
            </div>
        )
    }
}
export default App
  • 我们首次运行后发现,render() has been executed被打印了一次,说明render执行了一次,这是正常的,首席渲染
  • 然后我们点击button,发现render() has been executed又被打印了一次,说明render又执行了一次渲染,但是我们在button里面没有改变state的状态,num还是0,render理论上不执行才是最好的选择
  • 这时候shouldComponentUpdate就派上用场了
  • 我们只要在shouldComponentUpdate里面判断state是否发生了变化,如果没有,返回false,这样render就不会执行了
  • 上代码
js 复制代码
import React from 'react'
class App extends React.Component{
    constructor(props){
        super(props)
        this.state={
            num:0
        }
    }
    shouldComponentUpdate(nextProps,nextState,nextContent){
        //nextProps表示即将接受的新的props
        //nextState表示即将接受的心得State,
        //这里我们通过判断nextState.num === this.state.num,如果是返回false,阻止render执行;如果true,render继续渲染
        if(nextState.num ===  this.state.num){
            return false 
        }else{
            return true
        }
    }
    handleClick=()=>{
        this.setState({
            num:0
        })
    }
    render(){
        console.log('render() has been executed')
        return(
            <div>
                <div>{this.state.num}</div>
                <button onClick={this.handleClick}>button</button>
            </div>
        )
    }
}
export default App
  • 这时候我们再运行,首次还是会打印render() has been executed,说明render首次渲染,这是正常的情况
  • 然后我们点击button,发现render() has been executed没有再被打印,说明我们通过shouldComponentUpdate已经阻止了本次render渲染,应为状态没有发生变化
  • 这时候我们改变一下handleClick方式,让发state状态发生变化,看render会不会执行
js 复制代码
import React from 'react'
class App extends React.Component{
    constructor(props){
        super(props)
        this.state={
            num:0
        }
    }
    shouldComponentUpdate(nextProps,nextState,nextContent){
        //nextProps表示即将接受的新的props
        //nextState表示即将接受的心得State,
        //这里我们通过判断nextState.num === this.state.num,如果是返回false,阻止render执行;如果true,render继续渲染
        if(nextState.num ===  this.state.num){
            return false 
        }else{
            return true
        }
    }
    handleClick=()=>{
        this.setState({
            num:this.state.num+1
        })
    }
    render(){
        console.log('render() has been executed')
        return(
            <div>
                <div>{this.state.num}</div>
                <button onClick={this.handleClick}>button</button>
            </div>
        )
    }
}
export default App
  • 运行,点击button发现打印render() has been executed,说明render执行渲染了,因为state发生变化了
  • 如此,shouldComponentUpdate逻辑验证完毕

2.2 上父子组件,验证props

  • 直接上代码
js 复制代码
import React from 'react'
//定义子组件
class Child extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        console.log(' Child render() has been executed')
        return (
            <div>childNum :{this.props.childNum}</div>
        )
    }
}
class App extends React.Component{
    constructor(props){
        super(props)
        this.state={
            num:0,
            childNum:0
        }
    }   
    handleClick=()=>{
        this.setState({
            num:this.state.num+1
        })
    }
    render(){
        return(
            <div>
                <div>num :{this.state.num}</div>
                <Child childNum={this.state.childNum}></Child>
                <button onClick={this.handleClick}>button</button>
            </div>
        )
    }
}
export default App
  • 我们运行,首次子组件render会执行,打印Child render() has been executed

  • 然后我们点击父组件的button给num自增1,这时候父组件因为state发生变化了,要执行 render,但是传递给子组件的props并没有发生变化,我们没有对ChildNum做任何处理

  • 但是我们会发现,点击button的时候打印了Child render() has been executed,说明子组件的render执行了,子组件被重新渲染了,这不是我们想要的

  • 我们希望props不发生变化的时候,子组件不执行render,这首shouldComponentUpdate该上场了

js 复制代码
import React from 'react'
//定义子组件
class Child extends React.Component{
    constructor(props){
        super(props)
    }
    shouldComponentUpdate(nextProps,nextState,nextContext){
        //nextProps表示即将接受的新的props
        //nextState表示即将接受的心得State,
        //这里我们通过判断nextState.num === this.state.num,如果是返回false,阻止render执行;如果true,render继续渲染
        if(nextProps.childNum === this.props.childNum){
            return false
        }else{
            return true
        }
    }
    render(){
        console.log(' Child render() has been executed')
        return (
            <div>childNum :{this.props.childNum}</div>
        )
    }
}
class App extends React.Component{
    constructor(props){
        super(props)
        this.state={
            num:0,
            childNum:0
        }
    }   
    handleClick=()=>{
        this.setState({
            num:this.state.num+1
        })
    }
    render(){
        return(
            <div>
                <div>num :{this.state.num}</div>
                <Child childNum={this.state.childNum}></Child>
                <button onClick={this.handleClick}>button</button>
            </div>
        )
    }
}
export default App
  • 我们再运行,点击button,发现子组件的render方法并没有执行,因为props没有发生变化
  • 这时候我们改变一下父组件的handleClick方法,让childNum也变化一下,看子组件render会不会执行
js 复制代码
import React from 'react'
//定义子组件
class Child extends React.Component{
    constructor(props){
        super(props)
    }
    shouldComponentUpdate(nextProps,nextState,nextContext){
        //nextProps表示即将接受的新的props
        //nextState表示即将接受的心得State,
        //这里我们通过判断nextState.num === this.state.num,如果是返回false,阻止render执行;如果true,render继续渲染
        if(nextProps.childNum === this.props.childNum){
            return false
        }else{
            return true
        }
    }
    render(){
        console.log(' Child render() has been executed')
        return (
            <div>childNum :{this.props.childNum}</div>
        )
    }
}
class App extends React.Component{
    constructor(props){
        super(props)
        this.state={
            num:0,
            childNum:0
        }
    }   
    handleClick=()=>{
        this.setState({
            num:this.state.num+1,
            childNum:this.state.childNum+1//让子组件的props也发生变化
        })
    }
    render(){
        return(
            <div>
                <div>num :{this.state.num}</div>
                <Child childNum={this.state.childNum}></Child>
                <button onClick={this.handleClick}>button</button>
            </div>
        )
    }
}
export default App
  • 运行,点击button,发现子组件的render又被执行了,完美,props验证完毕

2. PureComponent

2.1 单组件验证state

  • 上面我们通过在shouldComponent先state和props的判断逻辑,决定要不要执行render,这样的问题在于每次都要写,就比较麻烦,如果React能提供一套内置的比较逻辑,这样我们就不用每次写了,有没有这样的功能呢,有,那就是PureComponent
  • PureComponent通过浅比较props和state自动实现shouldComponentUpdate,从而决定render是否执行,他是React.Component的一个变体,我们直接实现继承就可以实现
  • 浅比较比较好理解吧,不懂的自行研究一下,因为是浅比较,所以PureComponent适合一下数据结构简单扁平的基本类型
  • 话不多说上代码
js 复制代码
import React from 'react'
class App extends React.PureComponent{
    constructor(props){
        super(props)
        this.state={
            num:0
        }
    }
    handleClick=()=>{
        this.setState({
            num:0
        })
    }
    render(){
        console.log('render() has been executed')
        return(
            <div>
                <div>{this.state.num}</div>
                <button onClick={this.handleClick}>button</button>
            </div>
        )
    }
}
export default App
  • 运行,点击button,发现render不行执行,
  • 改变handleClick,再次验证
js 复制代码
import React from 'react'
class App extends React.PureComponent{
    constructor(props){
        super(props)
        this.state={
            num:0
        }
    }
    handleClick=()=>{
        this.setState({
            num:this.state.num+1
        })
    }
    render(){
        console.log('render() has been executed')
        return(
            <div>
                <div>{this.state.num}</div>
                <button onClick={this.handleClick}>button</button>
            </div>
        )
    }
}
export default App
  • 运行点击button,发现render被执行,验证完毕

2.2 父子组件验证props

  • 类比结合1.2和2.1,小伙伴们资兴市实现以下,不会的留言解答

3.React.memo

  • 上面我们讨论的都是类组件的state和props的变化产生的render性能优化
  • 针对函数组件,React提出了memo
  • memo的作用可以理解为只比较props的PureComponent,因为函数组件没有状态么
  • 上代码
js 复制代码
import React from 'react'
const Child=function Child(props){
    console.log('child function has been executed')
    return (
        <div>num from props:{props.num}</div>
    )
}
function App(props){
    const [num,setNum]=React.useState(0)
    const [count,setCount]=React.useState(0)
    return(
        <div>
            <div>count:{count}</div>
            <Child num={num}></Child>
            <button onClick={()=>{setCount(count=>count+1)}}>button</button>
        </div>
    )
}
export default App
  • 运行,首次渲染子组件,打印child function has been executed
  • 点击button,打印child function has been executed,说明子组件又被重新渲染了,是因为虽然num的值没有变化,但是count的值改变导致父组件重新渲染,进一步导致子组件重新渲染
  • 这时候我们加个React.memo,让props不发生变化的情况下,子组件不被重新渲染
js 复制代码
import React from 'react'
const Child=React.memo(function Child(props){
    console.log('child function has been executed')
    return (
        <div>num from props:{props.num}</div>
    )
})
function App(props){
    const [num,setNum]=React.useState(0)
    const [count,setCount]=React.useState(0)
    return(
        <div>
            <div>count:{count}</div>
            <Child num={num}></Child>
            <button onClick={()=>{setCount(count=>count+1)}}>button</button>
        </div>
    )
}
export default App
  • 运行,点击button,不打印,子组件不重新渲染
  • 然后改变父组件的num,让num也变化起来,看看子组件渲染情况
js 复制代码
import React from 'react'
const Child=React.memo(function Child(props){
    console.log('child function has been executed')
    return (
        <div>num from props:{props.num}</div>
    )
})
function App(props){
    const [num,setNum]=React.useState(0)
    const [count,setCount]=React.useState(0)
    return(
        <div>
            <div>count:{count}</div>
            <Child num={num}></Child>
            <button onClick={()=>{setCount(count=>count+1);setNum(num=>num+1)}}>button</button>
        </div>
    )
}
export default App
  • 运行,点击button,子组件重新被渲染,验证完毕

4. 总结

  • 根据不同的业务场景和数据结构,选用不同的render性能优化方式
相关推荐
吃手机用谁付的款19 分钟前
HTML常见标签
前端·html
好好研究27 分钟前
CSS样式中的布局、字体、响应式布局
前端·css
拉不动的猪2 小时前
前端小白之 CSS弹性布局基础使用规范案例讲解
前端·javascript·css
伍哥的传说2 小时前
React强大且灵活hooks库——ahooks入门实践之开发调试类hook(dev)详解
前端·javascript·react.js·ecmascript·hooks·react-hooks·ahooks
界面开发小八哥2 小时前
界面控件Kendo UI for Angular 2025 Q2新版亮点 - 增强跨设备的无缝体验
前端·ui·界面控件·kendo ui·angular.js
枷锁—sha3 小时前
从零掌握XML与DTD实体:原理、XXE漏洞攻防
xml·前端·网络·chrome·web安全·网络安全
F2E_Zhangmo3 小时前
基于cornerstone3D的dicom影像浏览器 第二章,初始化页面结构
前端·javascript·vue·cornerstone3d·cornerstonejs
代码的余温4 小时前
如何区别HTML和HTML5?
前端·html·html5
天下无贼!4 小时前
【样式效果】纯CSS从零到一实现动态彩色背景效果
前端·css
DoraBigHead4 小时前
手写 `new`、`call`、`apply`、`bind` + V8 函数调用机制解密
前端·javascript·面试