React 性能优化:深入理解 memo、useCallback 和 useMemo

506 阅读5分钟

引言

性能优化是软件开发中永远不会过时的话题,本篇将介绍在React编码过程中3个性能优化点:memouseCallbackuseMemo

56556.jpg

memo

memo 是 React 提供的一个高阶组件(HOC),用于缓存函数组件。它的作用是防止组件在父组件重新渲染时,不必要的重新渲染。memo 会对组件的 props 进行浅比较,如果 props 没有变化,组件就不会重新渲染。

  • 组件渲染成本较高:当组件的渲染成本较高时(例如,组件内部有复杂的计算或大量的子组件),使用 memo 可以显著提高性能。
  • props 不经常变化:当组件的 props 不经常变化时,使用 memo 可以避免不必要的渲染。
  • 浅比较memo 默认使用浅比较来比较 props。如果 props 是对象或数组,浅比较可能无法检测到深层的变化。在这种情况下,可以自定义比较函数。
import { memo } from "react";

interface BobProps {
  count: number;
  callback: () => void;
}

function Bob(props: BobProps) {
  console.log('Bob------');
  return <h2>{props.count}</h2>;
}

const MemoBob = memo(Bob);

在这个例子中,Bob 组件被 memo 包裹,生成了一个新的组件 MemoBob。当父组件重新渲染时,如果 MemoBob 的 props 没有变化,Bob 组件就不会重新渲染。

useCallback

useCallback 是 React 提供的一个钩子,用于缓存函数。它的作用是避免在每次渲染时都重新创建函数,从而减少不必要的渲染。

  • 函数作为 props 传递:当函数作为 props 传递给子组件时,使用 useCallback 可以避免子组件不必要的渲染。
  • 依赖项变化时重新创建函数:当函数的依赖项变化时,使用 useCallback 可以确保函数只在依赖项变化时重新创建。
  • 依赖项useCallback 的第二个参数是依赖项数组。如果依赖项发生变化,函数会重新创建。
import { useCallback } from "react";

function Aaa() {
  const [count, setCount] = useState(1);

  const bbbCallback = useCallback(() => {
    console.log('bbbCallback');
  }, []);

  return (
    <div>
      <MemoBob count={count} callback={bbbCallback} />
    </div>
  );
}

在这个例子中,bbbCallback 函数被 useCallback 缓存。由于 useCallback 的依赖项为空数组,bbbCallback 函数只会在组件挂载时创建一次,后续渲染时不会重新创建。

useMemo

useMemo 是 React 提供的一个钩子,用于缓存计算结果。它的作用是避免在每次渲染时都重新计算值,从而减少不必要的计算。

  • 计算成本较高:当计算成本较高时(例如,复杂的数学运算或数据处理),使用 useMemo 可以显著提高性能。
  • 依赖项变化时重新计算:当计算结果的依赖项变化时,使用 useMemo 可以确保计算结果只在依赖项变化时重新计算。
import { useMemo } from "react";

function Aaa() {
  const [count1, setCount1] = useState(1);
  const [count2, setCount2] = useState(2);

  const product = useMemo(() => {
    console.log('/////');
    return count1 * count2;
  }, [count1, count2]);

  return (
    <div>
      <p>Count1: {count1}</p>
      <button onClick={() => setCount1(count1 + 1)}>增加Count1</button>
      <p>Count2: {count2}</p>
      <button onClick={() => setCount2(count2 + 1)}>增加Count2</button>
      <p>乘积: {product}</p>
    </div>
  );
}

在这个例子中,product 的值被 useMemo 缓存。只有当 count1count2 发生变化时,product 才会重新计算。

4. 综合应用

在实际项目中,memouseCallbackuseMemo 通常会结合使用,以达到最佳的性能优化效果。以下是一个综合应用的示例:

// memo 性能优化 缓存 减少不必要的渲染
// useCallback 缓存函数 
// useMemo 缓存计算结果
import { useEffect,useState,memo,useCallback,useMemo } from "react";

// 面向对象的核心概念
// 接口 定义对象结构 确保实现该接口的类或对象 具有特定的属性和方法
// 满足接口 安全
interface BobProps {
  count: number;
  callback: () => void;
}

// 没有必要更新的组件能不能不更新?
// 函数参数要给类型约定
function Bob(props: BobProps) {
  console.log('Bob------');
  return <h2>{props.count}</h2>
}

// 缓存
// 参数是一个组件 将一个组件返回一个新组件的函数 叫做高阶组件
// 函数的参数或返回值是函数 叫做高阶函数
const MemoBob = memo(Bob);
// console.log(MemoBob);
const MemoExample = () => {
  const [count1,setCount1] = useState(1);
  const [count2,setCount2] = useState(2);
  const [count3,setCount3] = useState(3);
  const product = useMemo(() => {
    console.log('/////');
    return count1 * count2;
  },[count1,count2])
  return (
    <div>
      <p>Count1:{count1}</p>
      <button onClick={() => setCount1(count1 + 1)}>增加Count1</button>
      <p>Count2:{count2}</p>
      <button onClick={() => setCount2(count2 + 1)}>增加Count2</button>
      <p>Count3:{count3}</p>
      <button onClick={() => setCount3(count3 + 1)}>增加Count3</button>
      <p>乘积{product}</p>
    </div>
  )
}

function Aaa() {
  // 响应式数据发生改变之后 整个函数会重新执行
  const [count,setCount] = useState(1);
  const [num, setNum] = useState(1);  // useState 会特殊处理 不会每次都重新执行
  console.log('-----');
  // mounted
  useEffect(() => {
    setInterval(() => {
      setNum(Math.random())
    },2000)
    setTimeout(() => {
      setCount(3)
    },1000)
  },[]); // 挂载和卸载时执行 [] 也不会重新执行

  // 函数会重新创建
  // useCallback 缓存函数
  // useMemo 缓存计算结果
  // 第二个参数是依赖项
  const bbbCallback = useCallback(() => {
    console.log('bbbCallback');
    // count num
  },[])

  const count2 = useMemo(() => {
    return count*10;
  }, [count])

  // 重新执行
  return (
    <div>
      {num}
      {/* <Bob count = {2}></Bob> */}
      <MemoBob count={count2} callback={bbbCallback}/>
      <MemoExample />
    </div>
  )
}

export default Aaa;

5. 总结

React 提供了多种工具来帮助我们优化性能,其中 memouseCallbackuseMemo 是最常用的几种。通过合理地使用这些工具,我们可以显著提高应用的性能,尤其是在处理大型应用或复杂组件时。然而,需要注意的是,这些工具本身也有一定的性能开销,因此只有在确实需要时才使用它们。

在实际项目中,我们应该根据具体的场景和需求,灵活地使用这些工具,以达到最佳的性能优化效果。同时,我们还应该注意代码的可读性和可维护性,避免过度优化导致代码难以理解和维护。

希望本文能够帮助你更好地理解 memouseCallbackuseMemo,并在实际项目中应用它们,提升你的 React 应用性能。