React项目开发过程中需要注意避免re-render——React性能优化方案

前言

下面相关测试例子我都写在了仓库中,可以直接拿来调试。


首先我们要知道哪些方法会触发react组件的重新渲染(默认情况下)?

1、setState方法被调用(hook中是 useState中的setXXXX方法被调用)组件就会触发render,除了设置state为null的情况不会触发render。
注意注意!!上面说的是方法被调用就会re-render,而不指的是state数据发生改变才会re-render。意思就是说如果你点击一个按钮但是一直是 this.setState({ name: ‘winne’ }),那么你点多少次组件就会re-render多少次。

2、父组件重新渲染时,内部的所有子组件默认都会重新渲染,触发render。

重新渲染render会做些什么?
1、会对新l日VNode进行对比,也就是我们所说的DoM diff。
2、对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面
3、遍历差异对象,根据差异的类型,根据对应对规则更新VNode
React的处理render的基本思维模式是每次一有变动就会去重新渲染整个应用。在Virtual DOM没有出现之前,最简单的方法就是直接调用innerHTML。Virtual DOM厉害的地方并不是说它比直接操作DOM快,而是说不管数据怎么变,都会尽量以最小的代价去更新DOM。React将render函数返回的虚拟DOM树与老的进行比较,从而确定DOM要不要更新、怎么更新。当DOM树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层setState 一个微小的修改,默认会去遍历整棵树。尽管React使用高度优化的Diff 算法,但是这个过程仍然会损耗性能

现在分react的类组件和hook函数组件进行优化讲解。

一、react class 组件

1、PureComponent和shouldComponentUpdate

使用 PureComponent,每次对下一个的props和state进行一次浅比较(基本类型比较值是否相等,引用类型则比较引用地址是否相等)。当然,除了 PureComponent 外,我们还可以使用Component配合 shouldComponentUpdate 生命周期函数进行更深层次的控制。

PureComponent具体用例好文推荐:点击这里

shouldComponentUpdate来决定组件是否重新渲染,如果不希望组件重新渲染,在逻辑中返回false即可。

react官网性能优化篇幅:点击这里

其他的优化点和下面的hook组件有些雷同,只是语法不同,对比一下

二、react hook 组件(函数组件)

注意噢! 函数组件的重新渲染(re-render)都会执行整个函数其内部的所有逻辑。

1、多挖掘能使用useRef的地方

在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?第一个想到的应该是state。没错,一个组件的state可以在多次渲染之后依旧不变。但是,state的问题在于一旦修改了它就会造成组件的重新渲染,如果这个组件内部还有很多子组件,那么意味着所有的子组件也会被重新渲染
那么这个时候就可以使用useRef来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染

注意注意!!
但这并不意味了我们要全部使用useRef,因为你修改了useRef的值视图层是不会重新渲染更新的,所以如果你的变量是决定视图图层渲染的变量,请使用useState。其他用途使用useRef。

相关好文推荐:点击这里

2、正确使用useEffect的第二个参数

useEffect不加第二个参数的时候默认会在每次渲染后都执行。所以我们需要添加第二个参数列表进行控制
第二个参数列表中设置的变量为:每次列表中的变量改变了才会执行useEffect中的逻辑,否则就会跳过。

3、使用React.memo来控制整个函数组件的渲染时机

React.memo是React 16.6新的一个API,用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与PureComponent十分类似,但不同的是,React.memo只能用于函数组件,我们的hook组件就是一个函数组件。当然 react class中也有函数组件的存在,memo语法相同

import React, {
    useState, memo } from 'react';

const Child = (props) => {
   
    const [value, setValue] = useState(1);

    console.log('Child组件渲染了');

    return (
        <div>
            我是子组件Child
        </div>
    );
};

// 不使用memo时我们一般这样导出组件
// export default Child;

// 使用memo时我们可以这样写
const compareProps = (prevProps, nextProps) => {
   
    // 这里写入判断渲染的逻辑控制。返回true为不渲染组件,false为渲染组件;
    if (prevProps.count !== nextProps.count) {
   
        return false;
    }
    return true;
};
// memo接收两个参数,第一个参数是函数组件,第二个参数是比较props从而控制组件是否渲染的函数。
// 如果第二个参数不传递,则默认只会进行 props 的浅比较。
export default memo(Child, compareProps);

相关好文推荐:点击这里

4、使用 useMemo() 进行细粒度性能优化

上面 React.memo() 的使用我们可以发现,最终都是在最外层包装了整个组件,并且需要手动写一个方法比较那些具体的 props 不相同才进行 re-render。

而在某些场景下,我们只是希望 component 的部分不要进行 re-render,而不是整个 component 不要 re-render,也就是要实现 局部 Pure 功能。

useMemo() 基本用法如下:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo() 返回的是一个 memoized 值(变量值),只有当依赖项(比如上面的 a,b 发生变化的时候,才会重新计算这个 memoized 值)。

如果没有提供依赖数组(上面的 [a,b])则每次都会重新计算 memoized 值,也就会 re-render。

memoized 值不变的情况下,不会重新触发渲染逻辑。

说起渲染逻辑,需要记住的是 useMemo() 是在 render 期间执行的,所以传给 useMemo 的函数是在渲染期间运行的。不能进行一些额外的副操作,比如网络请求等。副作用属于 useEffect,而不是 useMemo。

大白话:就是说在 Hook组件中如果你在 return 返回的jsx中使用到了一个变量,这个变量(可以是一个js变量;也可以是jsx组件)需要进行一次很昂贵的计算的话,那么就考虑使用useMemo()来缓存。

下面贴代码方便测试:

/* eslint-disable react/jsx-one-expression-per-line */
import React, {
    useState, useMemo } from 'react';
import {
    Button } from 'antd';

const Com = () => {
   
    // 函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。

    const [value, setValue] = useState(1);
    const [name, setName] = useState('');

    const initName = () => {
   
        setName('winne');
    };

    const addValue = () => {
   
        setValue

你可能感兴趣的:(React,前端性能优化,React,性能优化,useMemo,memo,react,hook)