React学习笔记(七) React 性能优化,PureComponent、memo等以及函数组件

时间:2021-08-24
本文章向大家介绍React学习笔记(七) React 性能优化,PureComponent、memo等以及函数组件,主要包括React学习笔记(七) React 性能优化,PureComponent、memo等以及函数组件使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

不同类型业务要求的性能标准各不相同。如果对一个 ToB 的后台管理系统要求首屏速度以及 SEO,显然不合理也没必要。

第一要考虑的不是如何去优化,而是值不值得去优化,React 性能已经足够优秀,毕竟“过早优化是魔鬼”,情况总是“可以,但没必要”。

作为一个开发人员,深入了解工具不足之处,并拥有对其进行优化的能力,是极其重要的。

React 性能优化大抵可分为两点:

  1. 减少 rerender 次数 (immutable data、shouldComponentUpdate、PureComponent)
  2. 减轻 rerender 复杂度 (memoize-one)

本文还会介绍React16.6加入的另外一个专门用来优化函数组件(Functional Component) rerender 次数的方法: React.memo

1、减少 rerender 次数

无用的渲染

组件是构成React视图的一个基本单元。有些组件会有自己本地的状态(state), 当它们的值由于用户的操作而发生改变时,组件就会重新渲染。在一个React应用中,一个组件可能会被频繁地进行渲染。这些渲染虽然有一小部分是必须的,不过大多数都是无用的,它们的存在会大大降低我们应用的性能。

看下面这个例子:

import React from 'react';

class TestC extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }
  
    
    render() {
        return (
            <div >
            {this.state.count}
            <button onClick={()=>this.setState({count: 1})}>Click Me</button>
            </div>
        );
    }
}
export default TestC;

TestC组件有一个本地状态count,它的初始值是0(state = {count: 0})。当我们点击Click Me按钮时,count的值被设置为1。这时候屏幕的数字将会由0变成1。当我们再次点击该按钮时,count的值还是1, 这时候TestC组件本不应该被重新渲染,可是TestC组件还是会被重新渲染,这些就是所谓的无用渲染。

Pure Component/shouldComponentUpdate

为了避免React组件的无用渲染,我们可以实现自己的shouldComponentUpdate生命周期函数。

当React想要渲染一个组件的时候,它将会调用这个组件的shouldComponentUpdate函数, 这个函数会告诉它是不是真的要渲染这个组件。

如果我们的shouldComponentUpdate函数这样写:

shouldComponentUpdate(nextProps, nextState) {
    return true        
}

其中各个参数的含义是:

  • nextProps: 组件将会接收的下一个参数props
  • nextProps: 组件的下一个状态state

因为我们的shouldComponentUpdate函数一直返回true,这就告诉React,无论何种情况都要重新渲染该组件。

可是如果我们这么写:

shouldComponentUpdate(nextProps, nextState) {
    return false
}

因为这个方法的返回值是false,所以React永远都不会重新渲染我们的组件。

因此当你想要React重新渲染你的组件的时候,就在这个方法中返回true,否则返回false。现在让我们用shouldComponentUpdate重写之前的TestC组件:

import React from 'react';

class TestC extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }
    
    shouldComponentUpdate(nextProps, nextState) {
        if (this.state.count === nextState.count) {
            return false
        }
        return true
    }
    
    render() {
        return ( 
            <div> 
            { this.state.count } 
            <button onClick = {
                () => this.setState({ count: 1 }) }> Click Me </button> 
            </div>
        );
    }
}

export default TestC;

我们在TestC组件里添加了shouldComponentUpdate方法,判断如果现在状态的count和下一个状态的count一样时,我们返回false,这样React将不会进行组件的重新渲染,反之,如果它们两个的值不一样,就返回true,这样组件将会重新进行渲染。

PureComponent

React在v15.5的时候引入了Pure Component组件。React在进行组件更新时,如果发现这个组件是一个PureComponent,它会将组件现在的state和props和其下一个state和props进行浅比较,如果它们的值没有变化,就不会进行更新。要想让你的组件成为Pure Component,只需要extends React.PureComponent即可。

让我们用PureComponent去改写一下我们的代码吧:

import React from 'react';

class TestC extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }
    /*shouldComponentUpdate(nextProps, nextState) {
        if (this.state.count === nextState.count) {
            return false
        }
        return true
    }*/
    
    render() {
        return ( 
            <div> 
            { this.state.count } 
            <button onClick = {
                () => this.setState({ count: 1 })
            }> Click Me </button> 
            </div >
        );
    }
}

export default TestC;

在上面的代码中,我将shouldComponentUpdate的代码注释掉了,因为React.PureComponent本身就帮我们实现了一样的功能。

函数组件

上面我们探讨了如何使用PureComponentshouldComponentUpdate的方法优化类组件的性能。虽然类组件是React应用的主要组成部分,不过函数组件(Functional Component)同样可以被作为React组件使用。

function TestC(props) {
    return (
        <div>
            I am a functional component
        </div>
    )
}

对于函数组件,它们没有诸如state的东西去保存它们本地的状态(虽然在React Hooks中函数组件可以使用useState去使用状态), 所以我们不能像在类组件中使用shouldComponentUpdate等生命函数去控制函数组件的重渲染。当然,我们也不能使用extends React.PureComponent了,因为它压根就不是一个类。

要探讨解决方案,让我们先验证一下函数组件是不是也有和类组件一样的无用渲染的问题。

首先我们先将ES6的TestC类转换为一个函数组件:

import React from 'react';

const TestC = (props) => {
    console.log(`Rendering TestC :` props)
    return ( 
        <div>
            {props.count}
        </div>
    )
}
export default TestC;
// App.js
<TestC count={5} />

函数组件也有无用渲染的问题,如何优化呢?

使用React.memo()

React.memo(...)是React v16.6引进来的新属性。它的作用和React.PureComponent类似,是用来控制函数组件的重新渲染的。React.memo(...) 其实就是函数组件的React.PureComponent

如何使用React.memo(...)?

React.memo使用起来非常简单,假设你有以下的函数组件:

const Funcomponent = ()=> {
    return (
        <div>
            Hiya!! I am a Funtional component
        </div>
    )jsx
}

我们只需将上面的Funcomponent作为参数传入React.memo中:

const Funcomponent = ()=> {
    return (
        <div>
            Hiya!! I am a Funtional component
        </div>
    )
}
const MemodFuncComponent = React.memo(FunComponent)

React.memo会返回一个纯化(purified)的组件MemoFuncComponent,这个组件将会在JSX标记中渲染出来。当组件的参数props和状态state发生改变时,React将会检查前一个状态和参数是否和下一个状态和参数是否相同,如果相同,组件将不会被渲染,如果不同,组件将会被重新渲染。

现在让我们在TestC组件上使用React.memo进行优化:

let TestC = (props) => {
    console.log('Rendering TestC :', props)
    return ( 
        <div>
        { props.count }
        </>
    )
}
TestC = React.memo(TestC);

这样只要count的值不发生变化,组件就不会重复渲染

在我们之前那个没用到React.memo(...)的例子中,count的重复设置会使组件进行重新渲染。可是我们用了React.memo后,该组件在传入的值不变的前提下是不会被重新渲染的。

useCallback

说到了memo就再来谈一下hooks中的useCallback,useCallback返回一个缓存的函数,来看下最简单的用法

const fnA = useCallback(fnB, [a])

上面的useCallback会将我们传递给它的函数fnB返回,并且将这个结果缓存;当依赖a变更时,会返回新的函数。

useCallback的使用场景是:

有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

这样它就可以配合子组件的shouldComponentUpdate 或者 React.memo 起到减少不必要的渲染的作用。

React.memoReact.useCallback 记得需要配对使用,缺了一个都可能导致性能不升反“降”,毕竟无意义的浅比较也是要消耗那么一点点点的性能。

2、减轻 rerender 复杂度

memoize-one

memoize-one 本质是一个高阶函数,真正计算函数作为参数,返回一个新的函数,新的函数内部会缓存上一次入参以及上一次返回值,如果本次入参与上一次入参相等,则返回上一次返回值,否则,重新调用真正的计算函数,并缓存入参以及结果,供下一次使用。

基本使用

import memoize from "memoize-one";
 
const add = (a, b) => a + b; // 基本计算方法
const memoizedAdd = memoize(add); // 生成可缓存的计算方法
 
memoizedAdd(1, 2); // 3
 
memoizedAdd(1, 2); // 3
// Add 函数没有被执行:上一次的结果直接返回
 
memoizedAdd(2, 3); // 5
// Add 函数被调用获取新的结果
 
memoizedAdd(2, 3); // 5
// Add 函数没有被执行:上一次的结果直接返回
 
memoizedAdd(1, 2); // 3
// Add 函数被调用获取新的结果
// 即使该结果在之前已经缓存过了
// 但它并不是最近一次的缓存结果,所以缓存结果丢失了

memoize-one可以让我们在 React 中实现了类似与 Vue 计算属性(computed)的效果 —— 基于依赖缓存计算结果,达到减轻不必要 render 复杂度的效果。

原文地址:https://www.cnblogs.com/yongrizy/p/15180937.html