React中useMemo的性能优化实战:从原理到与useCallback、React.memo的配合使用

作者:安全边界时间:2026-10-27 00:22:02浏览:336

本文深入解析React中useMemo的核心机制,通过筛选偶数的实例演示如何避免高开销的重复计算。进一步探讨useCallback与React.memo的配合使用,解决因函数引用变化导致的无效重渲染问题,帮助开发者掌握缓存值与函数的最佳实践,提升应用性能。

概念

在react中,应用是通过不断的re-render(重新渲染)来确保页面上的数据能够及时更新。每一次重新渲染都可以被视为一个快照,就像拍摄照片一样,记录了特定时刻组件的状态。

useMemo

  • 将创建值的函数和依赖数组作为参数传递给useMemo。该Hook仅在依赖数组中的值发生变化时,才会重新计算并返回新的值。
  • 这种机制的主要作用是避免在每次渲染周期中都执行高开销的计算操作,从而提升性能。
  • 需要注意的是,内部的函数是在渲染阶段执行的,因此不应在其中执行副作用操作(如网络请求或DOM操作),这些操作应移至useEffect中。
  • 如果调用时没有提供依赖数组,那么每次渲染都会重新计算值,这就失去了优化的意义,等同于没有使用缓存。

栗子

筛选偶数

以下代码实现了找出0到count之间所有偶数的功能。其中count是一个可以动态修改的状态值,它会实时反映在页面上。当count的值发生变化时,会触发re-render(重新渲染)。

import React, { useState } from 'react';

export default () => {
  const [count, setCount] = useState(100);

  const arr = [];
  for (let i = 0; i < count; i++) {
    if (i % 2 === 0) {
      arr.push(i);
    }
  }

  return (
    <>
      <form>
        <label htmlFor="num">Your number:</label>
        <input
          type="number"
          value={count}
          onChange={(event) => {
            // 设置最大值为 100000
            let num = Math.min(100_000, Number(event.target.value));
            setCount(num);
          }}
        />
      </form>
      <p>
        有{arr.length}偶数在 0 到 {count} 之间:<span>{arr.join(', ')}</span>
      </p>
    </>
  );
};

每秒获取时间刷新页面

下面的代码增加了一个计时器,用于在页面上显示实时时间。这导致页面每秒都会发生一次re-render(重新渲染),进而每秒重新执行一次偶数筛选逻辑。尽管count(count状态)并没有改变,但计算过程依然重复执行。

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

export default () => {
  const [count, setCount] = useState(100);
  const [curTime, setCurTime] = useState('');

  const useTime = () => {
    useEffect(() => {
      const intervalId = window.setInterval(() => {
        let time = new Date();
        setCurTime(time.toLocaleString());
      }, 1000);

      return () => {
        window.clearInterval(intervalId);
      };
    }, []);

    return curTime;
  };
  const time = useTime();

  const arr = [];
  for (let i = 0; i < count; i++) {
    if (i % 2 === 0) {
      arr.push(i);
    }
  }

  return (
    <>
      <form>
        <div>{time}</div>
        <label htmlFor="num">Your number:</label>
        <input
          type="number"
          value={count}
          onChange={(event) => {
            // 设置最大值为 100000
            let num = Math.min(100_000, Number(event.target.value));
            setCount(num);
          }}
        />
      </form>
      <p>
        有{arr.length}偶数在 0 到 {count} 之间:<span>{arr.join(', ')}</span>
      </p>
    </>
  );
};

如何优化

我们的核心需求是:虽然每秒都在获取时间并触发重渲染,但如果count(count)的值没有变化,就没有必要反复计算偶数列表。特别是在count(count)数值非常大,或者在性能较弱的旧设备上运行时,这种重复计算会导致明显的卡顿,严重影响用户体验。引入后,解决这个问题变得非常简单。

我们对计算偶数的代码进行了改造:现在,只要count(count)保持不变,系统就不会重复执行计算逻辑,而是直接返回上一次的缓存结果。

const arr = useMemo(() => {
  const temp = [];
  for (let i = 0; i < count; i++) {
    if (i % 2 === 0) {
      temp.push(i);
    }
  }
  return temp;
}, [count]);

useCallback、React.memo

既然提到了,这里顺便介绍[[wp:aCallback]]和(React.memo)。它们的作用机制与类似,区别在于[[wp:aCallback]]返回的是缓存的函数,而用于组件级别的优化。

  • :当它包裹函数式组件且该组件作为子组件使用时,每次父组件更新后,会对传入的props进行浅比较。如果props没有变化,子组件将不会重新渲染。
  • [[wp:aCallback]]:其作用与一致,区别在于它返回并缓存一个函数引用。

下面看一个小栗子:

// 父组件代码:一个计时器每秒更新时间,父组件每秒不停的 re-render,改变 count 值的 onCountChange 函数传入子组件
import React, { useState, useEffect, useCallback } from 'react';
import Child from './child';

export default () => {
  const [count, setCount] = useState(100);
  const [curTime, setCurTime] = useState('');

  const useTime = () => {
    useEffect(() => {
      const intervalId = window.setInterval(() => {
        let time = new Date();
        setCurTime(time.toLocaleString());
      }, 1000);

      return () => {
        window.clearInterval(intervalId);
      };
    }, []);

    return curTime;
  };
  const time = useTime();
  const onCountChange = () => {
    setCount((count) => count + 100);
  };
  console.log('re-render-father');

  return (
    <>
      <div>{time}</div>
      <div>{count}</div>
      <Child onCountChange={onCountChange} />
    </>
  );
};

// 子组件代码,接收 onCountChange 函数,并且用 React.memo 包裹函数
import React from 'react';

export default React.memo((props: any) => {
  const { onCountChange } = props;
  console.log('re-render-child');

  return (
    <>
      <div
        onClick={() => {
          onCountChange();
        }}
      >
        加100
      </div>
    </>
  );
});

分析一下上面的栗子:

  • 现象:父组件和子组件都会不停地发生re-render。
  • 我在子组件中添加了,虽然父组件因为计时器在不停地re-render,但我每次传入子组件的onCountChange函数看起来是一样的。按照的逻辑,如果props没变就不应该re-render,为什么子组件还是会重新渲染呢?

原因:父组件在不停地re-render,每次渲染时都会创建一个新的函数实例。在JavaScript中,即使两个函数的代码完全相同,如果它们的引用地址不同,它们就不相等。因此,在进行浅比较时,认为props发生了变化,导致子组件重新渲染,造成了无效的优化。

解决办法:既然知道了原因,解决思路就是确保传入子组件的函数是同一个引用。这时就可以使用[[wp:aCallback]]来进行优化。

const onCountChange = useCallback(() => {
  setCount((count) => count + 100);
}, []);

或者可以使用:只需让返回一个函数即可达到相同效果。

const onCountChange = useMemo(() => {
  return () => {
    setCount((count) => count + 100);
  };
}, []);

由此可见,[[wp:aCallback]]可以被视为的一种语法糖。

将函数用[[wp:aCallback]]包裹后,会缓存该函数的引用。这样就不会每次重新创建函数,从而避免了子组件的重复re-render。

总结

  • 是一种优化代码的手段,它通过缓存计算结果或函数,减少重复计算和重复渲染,从而优化应用性能。
  • [[wp:aCallback]]的作用与基本一致,主要用于缓存函数引用。
  • 当或[[wp:aCallback]]配合使用时,可以解决因props中引用值变化导致的失效问题。通过缓存同一个引用,使得在浅比较时认为props未变,从而避免不必要的子组件re-render。

到此这篇关于快速学会react中useMemo的文章就介绍到这了,更多相关react中的useMemo内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

作者最新文章