React 记忆缓存优化

56 阅读9分钟

React 性能优化完整指南

前置文章:React 组件渲染

通过分析 useCallback、useMemo、React.memo、自定义 Hooks 和 Context 等核心概念,本文档深入探讨了 React 应用中常见的性能问题及其解决方案,提供了系统性的性能优化指导原则和最佳实践。

核心观点:90% 的 useMemo 和 useCallback 可以移除,大多数情况下它们是不必要的,甚至是有害的。

useCallback

useCallback 用于记忆化函数,防止在组件重新渲染时生成新的函数引用,从而避免不必要的子组件重新渲染。然而,在实际开发中,useCallback 经常被过度使用,导致代码复杂化而性能提升有限。

  • 完整记忆化链:只有当所有 props 和组件本身都被记忆化时,useCallback 才有意义;
  • 避免部分记忆化:一个未记忆化的 prop 会让整个记忆化失效;
  • 初始渲染开销:useCallback 在初始渲染时是有害的,会增加额外的开销,只有在重新渲染时才有价值;
推荐使用的场景
1、作为其他 Hooks 的依赖项
const Component = () => {
  const [count, setCount] = useState(0);

  const fetchData = useCallback(async () => {
    console.log("fetchData", count);
  }, [count]);

  useEffect(() => {
    fetchData();
  }, [fetchData]); // 只有当 count 改变时才重新获取数据

  return <button onClick={() => setCount(count + 1)}>Click me</button>
};
2、传递给被 React.memo 包装的子组件
const MemoizedChild = React.memo(({ onClick, data }: { onClick: () => void, data: { name: string } }) => {
  console.log("MemoizedChild 渲染");
  return <button onClick={onClick}>{data.name}</button>;
});

const Parent = () => {
  const [count, setCount] = useState(0);

  // 数据被 useMemo 记忆化
  const data = useMemo(() => ({ name: "test" }), []);

  // 函数被 useCallback 记忆化
  const handleClick = useCallback(() => {
    console.log("clicked");
  }, []);

  console.log("Parent 渲染");

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      {/* 只有 count 改变时,MemoizedChild 不会重新渲染 */}
      <MemoizedChild onClick={handleClick} data={data} />
    </div>
  );
};
3、复杂逻辑的事件处理函数、自定义 Hook 中返回的函数
const useCounter = () => {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  const decrement = useCallback(() => {
    setCount(prev => prev - 1);
  }, []);

  const reset = useCallback(() => {
    setCount(0);
  }, []);

  return { count, increment, decrement, reset };
};

const CustomHookExample = () => {
  const { count, increment, decrement, reset } = useCounter();

  return (
    <div>
      <span>Count: {count}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>重置</button>
    </div>
  );
};
不应该使用的场景
1、传递给 DOM 元素
const Component = () => {
  const [count, setCount] = useState(0);
  
  // DOM 元素不需要记忆化,useCallback 是多余的
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
};
2、传递给未记忆化的组件
// 2. 传递给未记忆化的组件
const Child = ({ onClick }) => {
  console.log('Child 渲染'); // 每次父组件渲染都会执行
  return <button onClick={onClick}>Click</button>;
};

const Parent = () => {
  const [count, setCount] = useState(0);
  
  // useCallback 在这里是无效的,因为 Child 没有记忆化
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      {/* Child 每次都会重新渲染,useCallback 没有意义 */}
      <Child onClick={handleClick} />
    </div>
  );
};

useMemo

useMemo 用于记忆化计算结果,避免在每次渲染时重复进行昂贵的计算。然而,与 useCallback 类似,useMemo 也经常被过度使用。许多开发者会将简单的 JavaScript 操作(如数组排序、对象创建)也包装在 useMemo 中,这不仅没有性能提升,反而增加了初始渲染的开销。

  • 初始渲染:useMemo 在初始渲染时是有害的,因为它需要额外的内存和时间来缓存结果。只有当计算成本超过缓存成本时,useMemo 才有价值;
  • 重新渲染:重新渲染子组件通常比 JavaScript 计算更昂贵
  • 建议:应该记忆化渲染树的重部分,而不是简单的计算。只对真正昂贵的计算使用 useMemo,简单操作反而会增加开销;

经过测试,发现在 6x CPU 减速的情况下,排序 250 个元素的数组只需要 不到 2 毫秒,而渲染这些元素需要超过 20 毫秒(10 倍以上)

推荐使用的情况
1、记忆化昂贵的计算
const ExpensiveComponent = ({ data }: { data: { value: number, weight: number }[] }) => {
  const expensiveValue = useMemo(() => {
    // 复杂的数学计算、大数据处理
    return data.reduce((acc, item) => {
      return acc + Math.pow(item.value, 2) * Math.sqrt(item.weight);
    }, 0);
  }, [data]);
  return <div>计算结果: {expensiveValue}</div>;
};
2、 记忆化渲染树的重部分
interface Item {
  id: number;
  name: string;
}

const List = ({ items }: { items: Item[] }) => {
  const content = useMemo(() => {
    console.log("重新渲染列表");
    return items.map((item) => (
      <ExpensiveChildComponent key={item.id} data={item} />
    ));
  }, [items]);

  return <div>{content}</div>;
};

const ExpensiveChildComponent = React.memo(({ data }: { data: Item }) => {
  console.log("子组件渲染:", data.id);
  return <div>{data.name}</div>;
});
3、作为其他钩子的依赖项
const Component = () => {
  const memoizedValue = useMemo(() => ({ id: 1, name: "test" }), []);
  useEffect(() => {
    // 只有当 memoizedValue 真正改变时才执行
    console.log("Value changed");
  }, [memoizedValue]);
};
不应该使用的情况
1、简单的 JavaScript 操作
const Component = ({ items }) => {
  // 数组排序是简单操作,不需要 useMemo
  const sortedItems = useMemo(() => {
    console.log("排序数组");
    return items.sort((a, b) => a.name.localeCompare(b.name));
  }, [items]);
  return (
    <div>
      {sortedItems.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
};

// 更好的做法:直接排序
const Component2 = ({ items }) => {
  const sortedItems = items.sort((a, b) => a.name.localeCompare(b.name));
  return (
    <div>
      {sortedItems.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
};
2、对象和数组的简单创建
const Component = ({ name }) => {
  const user = useMemo(() => ({ name, id: Date.now() }), [name]);
  const styles = useMemo(() => ({ color: "red", fontSize: "16px" }), []);
  return <div style={styles}>{user.name}</div>;
};

React.memo

React.memo 是一个高阶组件,用于记忆化组件的渲染结果,防止在相同 props 下不必要的重新渲染。React.memo 通过浅比较 props 来决定是否重新渲染组件。然而,React.memo 只有在特定场景下才有价值:当组件是纯展示组件且 props 变化不频繁时

React.memo 进行的是浅比较,这意味着它只比较 props 的第一层属性。如果 props 是复杂对象,需要确保这些对象的引用稳定。

  • 完整记忆化:只有当每个单个 prop 和组件本身都被记忆化时,才能防止重新渲染;
  • 浅比较:React.memo 进行的是浅比较,复杂对象需要确保引用稳定;
  • 纯组件:最适合纯展示组件,需要配合稳定的 props 引用,避免复杂状态逻辑;
推荐使用的情况
1、纯展示组件,props 变化不频繁
const UserCard = React.memo(
  ({
    user,
    onEdit,
  }: {
    user: { id: number; name: string; email: string };
    onEdit: (id: number) => void;
  }) => {
    console.log("UserCard 渲染:", user.id);
    return (
      <div>
        <h3>{user.name}</h3>
        <p>{user.email}</p>
        <button onClick={() => onEdit(user.id)}>Edit</button>
      </div>
    );
  }
);
2、列表项组件
const ListItem = React.memo(
  ({
    item,
    onSelect,
  }: {
    item: { id: number; title: string };
    onSelect: (id: number) => void;
  }) => {
    return <div onClick={() => onSelect(item.id)}>{item.title}</div>;
  }
);
3、与 useMemo/useCallback 配合使用
const Parent = () => {
  const [count, setCount] = useState(0);
  const [users, setUsers] = useState([
    { id: 1, name: "Alice", email: "alice@example.com" },
    { id: 2, name: "Bob", email: "bob@example.com" },
  ]);
  const handleEdit = useCallback((id) => {
    console.log("编辑用户:", id);
  }, []);
  const usersCards = useMemo(() => {
    return users.map((user) => (
      <UserCard key={user.id} user={user} onEdit={handleEdit} />
    ));
  }, [users, handleEdit]);

  console.log('Parent 渲染');
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      {usersCards}
    </div>
  );
};
不应该使用的情况
1、props 引用不稳定
const Parent = () => {
  const [count, setCount] = useState(0);
  // 每次渲染都创建新的对象,React.memo 失效
  const user = { id: 1, name: "Alice", email: "alice@example.com" };
  const handleEdit = (id) => console.log("编辑:", id);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      {/* UserCard 每次都会重新渲染,因为 props 引用不稳定 */}
      <UserCard user={user} onEdit={handleEdit} />
    </div>
  );
};
2、复杂状态组件
const ComplexComponent = React.memo(({ data }: { data: { name: string } }) => {
  const [internalState, setInternalState] = useState(0);
  const [loading, setLoading] = useState(false);

  const fetchData = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({ name: "John" });
      }, 1000);
    });
  };
  useEffect(() => {
    // 复杂的副作用逻辑
    setLoading(true);
    fetchData().then(() => setLoading(false));
  }, [data]);
  return (
    <div>
      {loading ? "Loading..." : data.name}
      <button onClick={() => setInternalState(internalState + 1)}>
       {internalState}
      </button>
    </div>
  );
});

自定义 Hooks

自定义 Hooks 是 React 中封装可复用逻辑的强大工具。它们可以帮助我们提取组件逻辑,提高代码的可读性和可维护性。然而,自定义 Hooks 也可能成为性能杀手,特别是当它们无意中将状态提升到更高的组件层级时。这种状态提升会导致不必要的重新渲染,影响整个应用的性能。

自定义 Hooks 中的任何状态改变都会导致宿主组件重新渲染,无论这个状态是否在返回值中暴露。这种状态传播是链式的:如果 Hook A 使用 Hook B,Hook B 的状态改变会导致 Hook A 重新渲染,最终导致宿主组件重新渲染。

  • 关键发现:自定义 Hooks 中的状态改变会向上传播,直到到达宿主组件,无论中间是否有记忆化。
  • 状态下沉:将状态移动到合适的组件层级,避免在高层级组件中使用包含状态的 Hooks
  • 避免内部状态:不要在 hooks 中维护不暴露的内部状态
  • 链式传播:hooks 链中的状态改变会向上传播,直到到达宿主组件
❌ 错误示例

状态提升导致整个页面重新渲染

const useModal = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [scroll, setScroll] = useState(0); // 内部状态
  const open = () => setIsOpen(true);
  const close = () => setIsOpen(false);
  // antd 的 Modal 组件
  const Dialog = () => <Modal open={isOpen} onCancel={close} />;
  return { isOpen, Dialog, open, close };
};

// 在 Page 组件中使用 自定义 hook
const Page = () => {
  const { Dialog, open } = useModal(); // 每次状态改变都会重新渲染整个 Page
  return (
    <div>
      <button onClick={open}>Open</button>
      <Dialog />
    </div>
  );
};
✅ 正确示例

状态下沉到小组件

const SettingsButton = () => {
  const { Dialog, open } = useModal(); // 状态被限制在这个小组件中
  return (
    <>
      <button onClick={open}>Open settings</button>
      <Dialog />
    </>
  );
};

const Page = () => {
  return (
    <div>
      <SettingsButton /> {/* 只有这个小组件会重新渲染 */}
    </div>
  );
};
❌ 链式状态传播问题
// Hook 链中的状态传播
const useScroll = (ref) => {
  const [scroll, setScroll] = useState(0);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const handleScroll = () => {
      setScroll(element?.scrollTop || 0);
    };

    element.addEventListener("scroll", handleScroll);
    return () => element.removeEventListener("scroll", handleScroll);
  });

  return scroll;
};

const useModal = () => {
  const [isOpen, setIsOpen] = useState(false);
  const ref = useRef(null);
  const scroll = useScroll(ref); // 滚动状态改变会导致整个链重新渲染

  const Dialog = () => <CustomModal open={isOpen} modalRef={ref} />;

  return { Dialog, open: () => setIsOpen(true) };
};
✅ 独立状态隔离
// 将滚动状态隔离到独立组件
const ModalBaseWithScroll = ({ isOpen, onClosed }) => {
  const ref = useRef(null);
  const scroll = useScroll(ref); // 滚动状态只影响这个组件

  console.log("滚动位置:", scroll);

  return <CustomModal open={isOpen} onCancel={onClosed} ref={ref} />;
};

const useModal = () => {
  const [isOpen, setIsOpen] = useState(false);

  const Dialog = () => (
    <ModalBaseWithScroll isOpen={isOpen} onClosed={() => setIsOpen(false)} />
  );

  return { Dialog, open: () => setIsOpen(true) };
};

Context 性能优化

Context 是 React 中用于跨组件传递数据的机制,它可以帮助我们避免 prop drilling 问题。然而,Context 也可能成为性能杀手,特别是当 Context 中的状态频繁改变时。Context 的工作原理是:当 Context 的值改变时,所有消费该 Context 的组件都会重新渲染。这种重新渲染是不可避免的,但我们可以通过合理的 Context 设计来最小化其影响。

状态分离原则:将状态和 API 分离,使用 Reducer 避免状态依赖,进一步拆分 Context

Context 性能优化的核心是将状态和 API 分离。状态是经常变化的,而 API(函数)通常是稳定的。通过将这两者分离到不同的 Context 中,我们可以确保只有真正需要状态的组件才会重新渲染。

性能优化策略

1. 状态和 API 分离
interface State {
  name: string;
  discount: number;
}

interface FormAPI {
  onSave: () => void;
  onDiscountChange: (discount: number) => void;
  onNameChange: (name: string) => void;
}
// 分离状态和 API
const FormDataContext = createContext<State>({} as State);
const FormAPIContext = createContext<FormAPI>({} as FormAPI);

const defaultState: State = {
  name: "John",
  discount: 30,
};
export const FormProvider = ({ children }: { children: ReactNode }) => {
  const [state, setState] = useState<State>(defaultState);

  // API 不依赖状态,保持稳定
  const api = useMemo(
    () => ({
      onSave: () => {},
      onDiscountChange: (discount: number) => {
        setState((prev) => ({ ...prev, discount }));
      },
      onNameChange: (name: string) => {
        setState((prev) => ({ ...prev, name }));
      },
    }),
    []
  ); // 空依赖数组

  return (
    <FormAPIContext.Provider value={api}>
      <FormDataContext.Provider value={state}>
        {children}
      </FormDataContext.Provider>
    </FormAPIContext.Provider>
  );
};

// 使用不同的 hooks
export const useFormData = () => useContext(FormDataContext);
export const useFormAPI = () => useContext(FormAPIContext);
2. 使用 Reducer 避免状态依赖
type Actions = { type: "updateName"; name: string } | { type: "updateDiscount"; discount: number };

const reducer = (state: State, action: Actions): State => {
  switch (action.type) {
    case "updateName":
      return { ...state, name: action.name };
    case "updateDiscount":
      return { ...state, discount: action.discount };
  }
};

export const FormProvider = ({ children }: { children: ReactNode }) => {
  const [state, dispatch] = useReducer(reducer, defaultState);

  // API 不依赖状态
  const api = useMemo(
    () => ({
      onSave: () => {
        // 保存逻辑
      },
      onDiscountChange: (discount: number) => {
        dispatch({ type: "updateDiscount", discount });
      },
      onNameChange: (name: string) => {
        dispatch({ type: "updateName", name });
      },
    }),
    []
  ); // 空依赖数组

  return (
    <FormAPIContext.Provider value={api}>
      <FormDataContext.Provider value={state}>
        {children}
      </FormDataContext.Provider>
    </FormAPIContext.Provider>
  );
};
3. 进一步拆分状态
// 将状态拆分为多个 Context
const FormNameContext = createContext<State['name']>({} as State['name']);
const FormDiscountContext = createContext<State['discount']>({} as State['discount']);

export const FormProvider3 = ({ children }: { children: ReactNode }) => {
  const [state, dispatch] = useReducer(reducer, defaultState);
  
  const api = useMemo(() => ({
    onSave: () => {
      // 保存逻辑
    },
    onDiscountChange: (discount: number) => {
      dispatch({ type: "updateDiscount", discount });
    },
    onNameChange: (name: string) => {
      dispatch({ type: "updateName", name });
    },
  }), []);

  return (
    <FormAPIContext.Provider value={api}>
      <FormNameContext.Provider value={state.name}>
          <FormDiscountContext.Provider value={state.discount}>
            {children}
          </FormDiscountContext.Provider>
      </FormNameContext.Provider>
    </FormAPIContext.Provider>
  );
};

// 分别使用不同的状态
export const useFormName = () => useContext(FormNameContext);
export const useFormDiscount = () => useContext(FormDiscountContext);

性能优化最佳原则

1. 先测量,后优化

  • 使用 React DevTools Profiler 等工具确认性能问题
  • 避免基于猜测进行优化
  • 建立性能基准和监控机制
// 使用 React DevTools Profiler 测量性能
import { Profiler, useRef, useEffect } from "react";

const onRenderCallback = (
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) => {
  console.log("Component:", id);
  console.log("Phase:", phase);
  console.log("Actual Duration:", actualDuration);
  console.log("Base Duration:", baseDuration);
  console.log("Start Time:", startTime);
  console.log("Commit Time:", commitTime);
};

// 自定义性能监控 Hook
const usePerformanceMonitor = (componentName) => {
  const renderCount = useRef(0);
  const startTime = useRef(performance.now());

  useEffect(() => {
    renderCount.current += 1;
    const endTime = performance.now();
    const duration = endTime - startTime.current;

    console.log(
      `${componentName} 渲染次数: ${
        renderCount.current
      }, 耗时: ${duration.toFixed(2)}ms`
    );

    startTime.current = performance.now();
  });

  return renderCount.current;
};

const MyComponent = () => {
  const renderCount = usePerformanceMonitor("MyComponent");

  return <div>渲染次数: {renderCount}</div>;
};

const App = () => {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MyComponent />
    </Profiler>
  );
};

export default App;

2. 避免过早优化

  • 只有在确实存在性能问题时才进行优化
  • 优先考虑其他优化方法(组件拆分、状态提升等)
  • 使用记忆化作为最后手段

3. 完整的记忆化链

  • 要么全部记忆化,要么都不记忆化
  • 部分记忆化是无效的,会浪费资源
  • 确保形成完整的记忆化链
import { useState, useMemo, useCallback } from "react";
import React from "react";


const Parent = () => {
  const [count, setCount] = useState(0);

  // 数据记忆化
  const data = useMemo(() => ({ name: "test" }), []);

  // 函数记忆化
  const handleClick = useCallback(() => {
    console.log("clicked");
  }, []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <MemoizedChild data={data} onClick={handleClick} />
    </div>
  );
};

const MemoizedChild = React.memo(({ data, onClick }: { data: { name: string }, onClick: () => void }) => {
  console.log("MemoizedChild 渲染");
  return <button onClick={onClick}>{data.name}</button>;
});


export default Parent;

4. 状态下沉

  • 将状态移动到合适的组件层级
  • 避免在高层级组件中使用包含状态的 Hooks
  • 最小化状态传播范围
import useModal from "antd/es/modal/useModal";

const SettingsButton = () => {
  const { Dialog, open } = useModal(); // 状态在小组件层级
  
  return (
    <>
      <button onClick={open}>Open</button>
      <Dialog />
    </>
  );
};

const App = () => {
  return (
    <div>
      <SettingsButton /> {/* 只有这个小组件会重新渲染 */}
    </div>
  );
};

export default App;

5. Context 分离原则

  • 将状态和 API 分离到不同的 Context
  • 使用 Reducer 避免状态依赖
  • 进一步拆分 Context 以精确控制重新渲染

完整的表单组件示例

import { Select, Slider } from "antd";
import {
  createContext,
  useContext,
  useReducer,
  useMemo,
  ReactNode,
} from "react";
import React from "react";

type Country = "USA" | "Canada" | "UK" | "Australia";

type FormState = {
  name: string;
  country: Country;
  discount: number;
};

type Actions =
  | { type: "updateName"; name: string }
  | { type: "updateCountry"; country: Country }
  | { type: "updateDiscount"; discount: number };

type FormAPI = {
  onSave: () => void;
  onNameChange: (name: string) => void;
  onCountryChange: (country: Country) => void;
  onDiscountChange: (discount: number) => void;
};

const formReducer = (state: FormState, action: Actions) => {
  switch (action.type) {
    case "updateName":
      return { ...state, name: action.name };
    case "updateCountry":
      return { ...state, country: action.country };
    case "updateDiscount":
      return { ...state, discount: action.discount };
  }
};

const defaultState: FormState = {
  name: "John",
  country: "USA",
  discount: 10,
};

// 1. 状态和 API 分离的 Context
const FormDataContext = createContext<FormState>({} as FormState);
const FormAPIContext = createContext<FormAPI>({} as FormAPI);

// 2. Provider 实现
export const FormProvider = ({ children }: { children: ReactNode }) => {
  const [state, dispatch] = useReducer(formReducer, defaultState);

  const api = useMemo(
    () => ({
      onSave: () => {
        // 保存逻辑
      },
      onNameChange: (name: string) => {
        dispatch({ type: "updateName", name });
      },
      onCountryChange: (country: Country) => {
        dispatch({ type: "updateCountry", country });
      },
      onDiscountChange: (discount: number) => {
        dispatch({ type: "updateDiscount", discount });
      },
    }),
    []
  );

  return (
    <FormAPIContext.Provider value={api}>
      <FormDataContext.Provider value={state}>
        {children}
      </FormDataContext.Provider>
    </FormAPIContext.Provider>
  );
};

// 3. 自定义 Hooks
export const useFormData = () => useContext(FormDataContext);
export const useFormAPI = () => useContext(FormAPIContext);

// 4. 优化的组件
const NameFormComponent = React.memo(() => {
  const { name } = useFormData();
  const { onNameChange } = useFormAPI();

  return (
    <div>
      <input value={name} onChange={(e) => onNameChange(e.target.value)} />
    </div>
  );
});

const CountryFormComponent = React.memo(() => {
  const { country } = useFormData();
  const { onCountryChange } = useFormAPI();

  return (
    <Select
      value={country}
      options={[
        { label: "USA", value: "USA" },
        { label: "Canada", value: "Canada" },
        { label: "UK", value: "UK" },
        { label: "Australia", value: "Australia" },
      ]}
      onChange={onCountryChange}
    />
  );
});

const DiscountFormComponent = React.memo(() => {
  const { onDiscountChange } = useFormAPI();

  return <Slider onChange={onDiscountChange} />;
});

// 5. 主表单组件
const Form = () => {
  return (
    <FormProvider>
      <div>
        <NameFormComponent />
        <CountryFormComponent />
        <DiscountFormComponent />
      </div>
    </FormProvider>
  );
};

export default Form;

性能监控示例

import { useRef, useEffect } from "react";

const usePerformanceMonitor = (componentName: string) => {
  const renderCount = useRef(0);
  const startTime = useRef(performance.now());

  useEffect(() => {
    renderCount.current += 1;
    const endTime = performance.now();
    const duration = endTime - startTime.current;

    console.log(
      `${componentName} 渲染次数: ${
        renderCount.current
      }, 耗时: ${duration.toFixed(2)}ms`
    );

    startTime.current = performance.now();
  });

  return renderCount.current;
};

// 使用示例
const MyComponent = () => {
  const renderCount = usePerformanceMonitor("MyComponent");

  return <div>渲染次数: {renderCount}</div>;
};

export default MyComponent;