React 性能优化方法useMemo、useCallback

在 React 中,可以使用useMemouseCallback来优化无用渲染的性能问题。本文是自己学习useMemouseCallback的笔记,介绍了它们的使用场景和区

1. useMemo

useMemo用来缓存,保存每次渲染时,不会发生变化的值,减少计算。在一些开销较大值不会变的场景下特别有用。

如下代码所示,定义了两个状态maxcount,在页面上显示当前maxcount的值,并可以点击按钮增加maxcount的值。

定义了一个函数getSum0~max的和,模拟一个开销大的操作,将求和的结果显示在页面上。但是这个求和的操作只依赖于max这个state,与其他值无关。

import { useState } from 'react';

const A = () => {
    const [max, setMax] = useState(100);
    const [count, setCount] = useState(0);

    // 模拟一个开销大的操作
    const getSum = () => {
        console.log('sum被重新计算了');
        let sum = 0;
        for (let i = 0; i <= max; i++) {
            sum += i;
        }
        return sum;
    };

    return (
        <>
            <div>sum:{getSum()}</div>
            <div>
                max:{max} <button onClick={() => setMax(max => max + 1)}>max++</button>
            </div>
            <div>
                count:{count}
                <button onClick={() => setCount(count => count + 1)}>count++</button>
            </div>
        </>
    );
};

export default A;
复制代码

更改count的值,求和结果sum并不会发生变化,因为求和的操作只依赖于max。若不使用useMemo进行优化,如下图所示,每次更改count的值,都会重新调用一次getSum函数,每次都进行了一次不必要的操作。

可以使用useMemo进行优化,如下代码所示,使用useMemo将函数包裹起来,并在useMemo的第二个参数中,写上max这个依赖项,表示只有当max发生变化时,才重新调用一次getSum函数,否则就使用上一次计算的值

import { useState, useMemo } from 'react';

const A = () => {
    const [max, setMax] = useState(100);
    const [count, setCount] = useState(0);

    // 模拟一个开销大的操作
    const getSum = useMemo(() => {
        console.log('sum被重新计算了');
        let sum = 0;
        for (let i = 0; i <= max; i++) {
            sum += i;
        }
        return sum;
        // 写上依赖项,只有当max发生变化时,才重新计算一次
    }, [max]);

    return (
        <>
            {/* 注意这里的写法 */}
            <div>sum:{getSum}</div>
            <div>
                max:{max} <button onClick={() => setMax(max => max + 1)}>max++</button>
            </div>
            <div>
                count:{count}
                <button onClick={() => setCount(count => count + 1)}>count++</button>
            </div>
        </>
    );
};

export default A;
复制代码

结果如下图所示,更新count时,并不会重新调用getSum。只有max变化时,才会重新调用getSum,减少了不必要的函数调用和渲染,实现了优化。

注意:

  • useMemo会返回一个值,所以写的是<div>sum:{getSum}</div>而不是<div>sum:{getSum()}</div>,不用自己调用,体会下这里的区别。
  • 传入useMemo的函数会在渲染期间执行,不要在这个函数内部执行与渲染无关的操作。这里只是为了演示,在函数内执行了console.log()
  • 不要忘记写正确的依赖数组。若没有提供依赖数组,useMemo在每次渲染时都会计算新的值。

2. useCallback

useCallback用来缓存函数。通常用于父子组件中,父组件传给子组件一个函数,父组件更新时,传递给子组件的函数也会被重新创建。有时候传递给子组件的函数没必要重新创建,useCallback就可以缓存这个函数,不使这个函数重新被创建

如下代码所示,父组件A内部创建了numcount两个变量,显示在页面上,并可以更新它们的值。创建了一个函数getCount,返回count的值。父组件A向子组件B传递getCount这个函数。

子组件B拿到getCount函数后,调用并将count值显示在页面上。为了演示每次更新时,getCount是否被重新创建,这里使用了Set这个数据结构。在B组件内部将getCount函数的引用存入Set,并显示Set的长度,长度增加则说明getCount函数被重新创建了。

import { useState } from 'react';

const set = new Set();

const A = () => {
    const [num, setNum] = useState(0);
    const [count, setCount] = useState(0);

    const getCount = () => count;

    return (
        <>
            <B getCount={getCount} />
            <div>
                count:{count}
                <button onClick={() => setCount(count => count + 1)}>count++</button>
            </div>
            <div>
                num:{num}
                <button onClick={() => setNum(num => num + 1)}>num++</button>
            </div>
        </>
    );
};

const B = ({ getCount }) => {
    set.add(getCount);
    return (
        <>
            <div>count:{getCount()}</div>
            <div>集合内元素数量:{set.size}</div>
        </>
    );
};

export default A;
复制代码

结果如下图所示,当num发生变化时,触发父组件A更新,传递给BgetCount函数也被重新创建了。然而getCount函数返回的是countnum发生变化没必要再重新创建一次getCount函数,这造成了性能的浪费。

可以使用useCallback进行性能优化,在创建getCount函数时,使用useCallback包裹,并写上依赖数组[count],表示只有当count变化时,才重新创建一次getCount函数。

import { useState, useCallback } from 'react';

const set = new Set();

const A = () => {
    const [num, setNum] = useState(0);
    const [count, setCount] = useState(0);

    const getCount = useCallback(() => count, [count]);

    return (
        <>
            <B getCount={getCount} />
            <div>
                count:{count}
                <button onClick={() => setCount(count => count + 1)}>count++</button>
            </div>
            <div>
                num:{num}
                <button onClick={() => setNum(num => num + 1)}>num++</button>
            </div>
        </>
    );
};

const B = ({ getCount }) => {
    set.add(getCount);
    return (
        <>
            <div>count:{getCount()}</div>
            <div>集合内元素数量:{set.size}</div>
        </>
    );
};

export default A;
复制代码

结果如下图所示,count更新时,传递给B组件的getCount函数被重新创建。而num更新时,getCount函数不会被重新创建,这减少了不必要的创建函数开销,实现了优化。

注意:

  • 因为useCallback返回的是一个函数,所以页面上还是要写<div>count:{getCount()}</div>,需要自己调用。体会一下与useMemo的区别。

以上是本人学习所得之拙见,若有不妥,欢迎指出交流!

猜你喜欢

转载自juejin.im/post/7041916037012389902
今日推荐