调试 React 组件可能会变得复杂,尤其是当你试图弄清事件耗时多久或一个方法被调用了多少次时。如果我们能添加一个简单的 Hook 来记录以下信息,岂不美哉:
- 组件挂载和卸载时间
- 页面重新加载
- 方法执行时长(以及调用次数)
这正是我们今天要构建的内容:一个自定义 Hook,名为 useDebugLogger
。
我们要构建的内容
我们要构建的 Hook 能够做到以下几点:
- 记录组件挂载所需的时间(以毫秒为单位)。
- 记录组件卸载的时间。
- 记录用户重新加载页面的时间。
- 记录任意函数(或异步方法)的执行时间以及被调用的次数。
它的使用方式如下:
jsx
const { logMethodExecution } = useDebugLogger("Home");
const myMethod = logMethodExecution("myMethod", () => {
// 你的逻辑代码
});
下面是我们的 useDebugLogger
Hook 的完整代码:
jsx
import { useEffect, useRef, useCallback } from "react";
const useDebugLogger = (componentName: string) => {
const methodExecutionCount = useRef<Record<string, number>>({});
// 记录挂载时间
useEffect(() => {
const mountStart = performance.now();
const mountEnd = performance.now();
console.log(
`🔸 [${componentName}] 在 ${(mountEnd - mountStart).toFixed(2)}ms 内挂载`
);
return () => {
const endTime = new Date().toLocaleTimeString();
console.log(`🔸 [${componentName}] 在 ${endTime} 卸载`);
};
}, [componentName]);
// 记录页面重新加载
useEffect(() => {
const reloadHandler = () => {
const reloadTime = new Date().toLocaleTimeString();
console.log(`🔸 [${componentName}] 在 ${reloadTime} 检测到重新加载`);
};
window.addEventListener("beforeunload", reloadHandler);
return () => {
window.removeEventListener("beforeunload", reloadHandler);
};
}, [componentName]);
// 方法包装器:记录执行时间和调用次数
const logMethodExecution = useCallback<
<T extends (...args: unknown[]) => unknown>(methodName: string, method: T) => T
>((methodName, method) => {
return ((...args: Parameters<T>): ReturnType<T> => {
methodExecutionCount.current[methodName] =
(methodExecutionCount.current[methodName] || 0) + 1;
const count = methodExecutionCount.current[methodName];
const start = performance.now();
console.log(
`🔹 [${componentName}] ${methodName} 开始 (第 ${count} 次执行)`
);
const result = method(...args);
if (result instanceof Promise) {
return result.finally(() => {
const end = performance.now();
console.log(
`🔹 [${componentName}] ${methodName} 在 ${(end - start).toFixed(
2
)}ms 后完成 (第 ${count} 次执行)`
);
}) as ReturnType<T>;
} else {
const end = performance.now();
console.log(
`🔹 [${componentName}] ${methodName} 在 ${(end - start).toFixed(
2
)}ms 后完成 (第 ${count} 次执行)`
);
return result as ReturnType<T>;
}
}) as T;
}, [componentName]);
return { logMethodExecution };
};
export default useDebugLogger;
逐步解析
1. 挂载时间记录
jsx
useEffect(() => {
const mountStart = performance.now();
const mountEnd = performance.now();
console.log(`[组件] 在 Xms 内挂载`);
}, []);
这部分代码记录了组件挂载所需的时间,使用 performance.now()
确保记录的准确性。
2. 卸载记录
jsx
return () => {
const endTime = new Date().toLocaleTimeString();
console.log(`[组件] 在 HH:MM:SS 卸载`);
};
这部分代码在组件从 DOM 中移除时记录卸载时间。
3. 重新加载追踪
jsx
useEffect(() => {
const reloadHandler = () => {
console.log(`[组件] 在 HH:MM:SS 检测到重新加载`);
};
window.addEventListener("beforeunload", reloadHandler);
return () => window.removeEventListener("beforeunload", reloadHandler);
}, []);
这部分代码在用户刷新浏览器时记录重新加载的时间。
4. 方法记录器
jsx
const logMethodExecution = useCallback((methodName, method) => {
return (...args) => {
// 记录开始时间
const start = performance.now();
// 记录调用次数
// 执行方法并记录持续时间
};
}, []);
每次方法执行时,你会得到以下信息:
- 调用次数
- 开始时间
- 执行时长(精确到毫秒)
在组件中的示例用法
jsx
const { logMethodExecution } = useDebugLogger("Home");
const fetchData = logMethodExecution("fetchData", async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const data = await res.json();
console.log("获取的数据:", data);
});
return (
<button onClick={fetchData}>获取数据</button>
);
控制台输出:
jsx
🔹 [主页] fetchData 开始 (第 1 次执行)
获取的数据: { id: 1, title: ... }
🔹 [主页] fetchData 在 412.77ms 后完成 (第 1 次执行)
总结
useDebugLogger Hook 为你提供了对 React 组件行为的细致洞察。它是一种轻量级且优雅的方式,在开发过程中追踪性能,无需借助外部工具。