Appearance
useDebugValue自定义调试
学习目标
通过本章学习,你将全面掌握:
- useDebugValue的概念和作用
- 在React DevTools中显示调试信息
- 自定义Hook的调试优化
- 格式化函数的使用和性能优化
- 调试复杂状态
- 性能监控技巧
- 最佳实践和常见模式
- TypeScript集成
- 生产环境的调试策略
- 与其他调试工具配合使用
第一部分:useDebugValue基础
1.1 什么是useDebugValue
useDebugValue可以在React DevTools中显示自定义Hook的标签,方便调试。
jsx
import { useDebugValue, useState } from 'react';
// 基本用法
function useCustomHook(value) {
useDebugValue(value);
const [state, setState] = useState(value);
return [state, setState];
}
// 在React DevTools中会显示:
// CustomHook: value值
// 使用
function Component() {
const [count] = useCustomHook(0);
return <div>{count}</div>;
}1.2 显示有意义的信息
jsx
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
// 在DevTools中显示在线状态
useDebugValue(isOnline ? '在线 ✅' : '离线 ❌');
return isOnline;
}
// 使用
function App() {
const isOnline = useOnlineStatus();
return (
<div>
网络状态: {isOnline ? '在线' : '离线'}
</div>
);
}1.3 格式化函数的重要性
jsx
// ❌ 不好:每次渲染都计算
function useBadDebug() {
const [user, setUser] = useState({
id: 1,
name: 'Alice',
email: 'alice@example.com',
role: 'admin',
permissions: ['read', 'write', 'delete'],
lastLogin: new Date()
});
// 即使DevTools未打开,也会每次计算
const debugInfo = `${user.name} (${user.role}) - ${user.permissions.length}权限`;
useDebugValue(debugInfo);
return [user, setUser];
}
// ✅ 好:使用格式化函数(延迟计算)
function useGoodDebug() {
const [user, setUser] = useState({
id: 1,
name: 'Alice',
email: 'alice@example.com',
role: 'admin',
permissions: ['read', 'write', 'delete'],
lastLogin: new Date()
});
// 只在DevTools打开时才执行格式化
useDebugValue(user, (u) => {
return `${u.name} (${u.role}) - ${u.permissions.length}权限 - 最后登录: ${u.lastLogin.toLocaleString()}`;
});
return [user, setUser];
}
// 性能优化:只在需要时格式化
function useExpensiveDebug() {
const [data, setData] = useState([]);
// 不推荐:每次渲染都计算总和
// useDebugValue(`${data.length} items, total: ${data.reduce((a,b) => a + b.value, 0)}`);
// 推荐:使用格式化函数
useDebugValue(data, (d) => {
if (d.length === 0) return 'Empty';
const total = d.reduce((sum, item) => sum + item.value, 0);
const avg = total / d.length;
return `${d.length} items | 总计: ${total} | 平均: ${avg.toFixed(2)}`;
});
return [data, setData];
}1.4 useDebugValue的工作原理
jsx
/**
* useDebugValue的内部机制:
*
* 1. 仅在开发模式下有效
* 2. 仅在React DevTools打开时才执行格式化函数
* 3. 不影响生产环境性能
* 4. 只能在自定义Hook中使用
*
* 执行时机:
* - 无格式化函数:每次渲染都记录值
* - 有格式化函数:只在DevTools检查时执行
*/
function useDebugValueDemo() {
const [count, setCount] = useState(0);
console.log('1. Hook渲染');
// 基础用法:直接传值
useDebugValue(count);
// 格式化用法:延迟计算
useDebugValue(count, (c) => {
console.log('2. 格式化函数执行(只在DevTools打开时)');
return `计数: ${c}`;
});
console.log('3. Hook渲染完成');
return [count, setCount];
}第二部分:实际应用场景
2.1 调试表单Hook
jsx
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitCount, setSubmitCount] = useState(0);
// 显示详细的表单状态摘要
useDebugValue(
{ values, errors, touched, isSubmitting, submitCount },
(state) => {
const errorCount = Object.keys(state.errors).length;
const touchedCount = Object.keys(state.touched).length;
const fieldCount = Object.keys(state.values).length;
const filledCount = Object.values(state.values).filter(v => v).length;
const status = state.isSubmitting ? '提交中' :
errorCount > 0 ? '有错误' :
filledCount === fieldCount ? '已完成' : '进行中';
return `表单(${status}): ${fieldCount}字段 | ${touchedCount}已访问 | ${filledCount}已填 | ${errorCount}错误 | ${state.submitCount}次提交`;
}
);
const handleChange = (field) => (e) => {
const value = e.target.value;
setValues(prev => ({
...prev,
[field]: value
}));
// 验证
if (validate) {
const fieldErrors = validate({ ...values, [field]: value });
setErrors(prev => ({
...prev,
[field]: fieldErrors[field]
}));
}
};
const handleBlur = (field) => () => {
setTouched(prev => ({
...prev,
[field]: true
}));
};
const handleSubmit = async (onSubmit) => async (e) => {
e.preventDefault();
setIsSubmitting(true);
setSubmitCount(prev => prev + 1);
try {
await onSubmit(values);
} finally {
setIsSubmitting(false);
}
};
return {
values,
errors,
touched,
isSubmitting,
submitCount,
handleChange,
handleBlur,
handleSubmit
};
}
// 使用
function FormExample() {
const form = useForm(
{ username: '', email: '', password: '' },
(values) => {
const errors = {};
if (!values.username) errors.username = '用户名必填';
if (!values.email) errors.email = '邮箱必填';
if (!values.password) errors.password = '密码必填';
return errors;
}
);
return (
<form onSubmit={form.handleSubmit(async (values) => {
console.log('提交:', values);
})}>
<input
value={form.values.username}
onChange={form.handleChange('username')}
onBlur={form.handleBlur('username')}
placeholder="用户名"
/>
{form.errors.username && <span>{form.errors.username}</span>}
<button type="submit" disabled={form.isSubmitting}>
{form.isSubmitting ? '提交中...' : '提交'}
</button>
</form>
);
}2.2 调试数据获取Hook
jsx
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [retryCount, setRetryCount] = useState(0);
const fetchStartTime = useRef(null);
const fetchDuration = useRef(0);
// 显示详细的请求状态
useDebugValue(
{ url, data, loading, error, retryCount, duration: fetchDuration.current },
(state) => {
if (state.loading) {
return `🔄 获取中: ${state.url} (尝试 #${state.retryCount + 1})`;
}
if (state.error) {
return `❌ 错误: ${state.error} (${state.url}, ${state.retryCount}次重试)`;
}
if (state.data) {
const dataSize = JSON.stringify(state.data).length;
return `✅ 成功: ${state.url} (${dataSize} bytes, ${state.duration}ms, ${state.retryCount}次重试)`;
}
return `⏸️ 未开始: ${state.url}`;
}
);
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
setLoading(true);
setError(null);
fetchStartTime.current = performance.now();
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (!cancelled) {
fetchDuration.current = Math.round(performance.now() - fetchStartTime.current);
setData(data);
setLoading(false);
}
} catch (err) {
if (!cancelled) {
setError(err.message);
setLoading(false);
}
}
};
fetchData();
return () => {
cancelled = true;
};
}, [url, retryCount]);
const retry = () => {
setRetryCount(prev => prev + 1);
};
return { data, loading, error, retry, retryCount };
}
// 使用
function DataComponent() {
const { data, loading, error, retry } = useFetch('/api/users');
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error} <button onClick={retry}>重试</button></div>;
if (!data) return null;
return (
<div>
<h2>用户列表</h2>
{/* 显示数据 */}
</div>
);
}2.3 调试本地存储Hook
jsx
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const [lastUpdated, setLastUpdated] = useState(new Date());
const [updateCount, setUpdateCount] = useState(0);
// 调试信息
useDebugValue(
{ key, value: storedValue, lastUpdated, updateCount },
(state) => {
const valueSize = JSON.stringify(state.value).length;
const timeSinceUpdate = Math.round((Date.now() - state.lastUpdated.getTime()) / 1000);
return `LocalStorage(${state.key}): ${valueSize} bytes | 更新${state.updateCount}次 | ${timeSinceUpdate}秒前`;
}
);
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
setLastUpdated(new Date());
setUpdateCount(prev => prev + 1);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
const removeValue = () => {
try {
window.localStorage.removeItem(key);
setStoredValue(initialValue);
setLastUpdated(new Date());
setUpdateCount(prev => prev + 1);
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue, removeValue];
}
// 使用
function SettingsComponent() {
const [settings, setSettings, removeSettings] = useLocalStorage('settings', {
theme: 'light',
language: 'zh-CN',
notifications: true
});
return (
<div>
<h2>设置</h2>
{/* 设置表单 */}
</div>
);
}2.4 调试定时器Hook
jsx
function useInterval(callback, delay) {
const savedCallback = useRef(callback);
const intervalId = useRef(null);
const [isRunning, setIsRunning] = useState(false);
const [tickCount, setTickCount] = useState(0);
const [startTime, setStartTime] = useState(null);
// 调试信息
useDebugValue(
{ delay, isRunning, tickCount, startTime },
(state) => {
if (!state.isRunning) {
return `⏸️ 已停止 | ${state.tickCount}次触发`;
}
const elapsed = state.startTime ? Math.round((Date.now() - state.startTime) / 1000) : 0;
const avgInterval = state.tickCount > 0 ? Math.round(elapsed / state.tickCount) : 0;
return `▶️ 运行中 | 间隔${state.delay}ms | ${state.tickCount}次触发 | 已运行${elapsed}秒 | 平均${avgInterval}秒/次`;
}
);
// 更新callback引用
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) return;
setIsRunning(true);
setStartTime(Date.now());
const tick = () => {
savedCallback.current();
setTickCount(prev => prev + 1);
};
intervalId.current = setInterval(tick, delay);
return () => {
clearInterval(intervalId.current);
setIsRunning(false);
};
}, [delay]);
return { isRunning, tickCount };
}
// 使用
function TimerComponent() {
const [count, setCount] = useState(0);
useInterval(() => {
setCount(c => c + 1);
}, 1000);
return <div>计数: {count}</div>;
}第三部分:高级调试模式
3.1 性能监控
jsx
function usePerformanceMonitor(componentName) {
const renderCount = useRef(0);
const renderTimes = useRef([]);
const lastRenderTime = useRef(null);
renderCount.current++;
const currentTime = performance.now();
if (lastRenderTime.current) {
renderTimes.current.push(currentTime - lastRenderTime.current);
}
lastRenderTime.current = currentTime;
// 性能统计
useDebugValue(
{ name: componentName, renderCount: renderCount.current, renderTimes: renderTimes.current },
(state) => {
if (state.renderTimes.length === 0) {
return `${state.name}: ${state.renderCount}次渲染`;
}
const avgTime = state.renderTimes.reduce((a, b) => a + b, 0) / state.renderTimes.length;
const maxTime = Math.max(...state.renderTimes);
const minTime = Math.min(...state.renderTimes);
const recentTime = state.renderTimes[state.renderTimes.length - 1];
return `${state.name}: ${state.renderCount}次 | 平均${avgTime.toFixed(2)}ms | 最近${recentTime.toFixed(2)}ms | 范围[${minTime.toFixed(2)}-${maxTime.toFixed(2)}]ms`;
}
);
return renderCount.current;
}
// 使用
function MonitoredComponent() {
usePerformanceMonitor('MonitoredComponent');
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>增加</button>
</div>
);
}3.2 状态历史追踪
jsx
function useStateWithHistory(initialValue, maxHistorySize = 10) {
const [value, setValue] = useState(initialValue);
const history = useRef([initialValue]);
const historyIndex = useRef(0);
// 调试信息:显示历史记录
useDebugValue(
{ value, history: history.current, index: historyIndex.current },
(state) => {
const canUndo = state.index > 0;
const canRedo = state.index < state.history.length - 1;
return `值: ${JSON.stringify(state.value)} | 历史: ${state.index + 1}/${state.history.length} | ${canUndo ? '可撤销' : '不可撤销'} | ${canRedo ? '可重做' : '不可重做'}`;
}
);
const setValueWithHistory = (newValue) => {
const actualValue = newValue instanceof Function ? newValue(value) : newValue;
// 移除当前位置之后的历史
history.current = history.current.slice(0, historyIndex.current + 1);
// 添加新值
history.current.push(actualValue);
// 限制历史大小
if (history.current.length > maxHistorySize) {
history.current.shift();
} else {
historyIndex.current++;
}
setValue(actualValue);
};
const undo = () => {
if (historyIndex.current > 0) {
historyIndex.current--;
setValue(history.current[historyIndex.current]);
}
};
const redo = () => {
if (historyIndex.current < history.current.length - 1) {
historyIndex.current++;
setValue(history.current[historyIndex.current]);
}
};
const canUndo = historyIndex.current > 0;
const canRedo = historyIndex.current < history.current.length - 1;
return {
value,
setValue: setValueWithHistory,
undo,
redo,
canUndo,
canRedo,
history: history.current
};
}
// 使用
function HistoryComponent() {
const state = useStateWithHistory(0);
return (
<div>
<p>值: {state.value}</p>
<button onClick={() => state.setValue(v => v + 1)}>增加</button>
<button onClick={state.undo} disabled={!state.canUndo}>撤销</button>
<button onClick={state.redo} disabled={!state.canRedo}>重做</button>
</div>
);
}3.3 依赖追踪
jsx
function useDependencyTracker(dependencies, hookName = 'Hook') {
const previousDeps = useRef(dependencies);
const renderCount = useRef(0);
const changedDeps = useRef([]);
renderCount.current++;
// 检测哪些依赖改变了
if (previousDeps.current) {
changedDeps.current = dependencies
.map((dep, index) => ({
index,
previous: previousDeps.current[index],
current: dep,
changed: !Object.is(dep, previousDeps.current[index])
}))
.filter(item => item.changed);
}
previousDeps.current = dependencies;
// 调试信息
useDebugValue(
{ hookName, renderCount: renderCount.current, changedDeps: changedDeps.current },
(state) => {
if (state.changedDeps.length === 0) {
return `${state.hookName}: 第${state.renderCount}次渲染 | 无依赖变化`;
}
const changes = state.changedDeps
.map(dep => `dep[${dep.index}]`)
.join(', ');
return `${state.hookName}: 第${state.renderCount}次渲染 | 变化: ${changes}`;
}
);
return changedDeps.current;
}
// 使用
function TrackedComponent({ userId, filter }) {
useDependencyTracker([userId, filter], 'TrackedComponent');
useEffect(() => {
console.log('Effect运行,因为userId或filter改变');
}, [userId, filter]);
return <div>User {userId}, Filter: {filter}</div>;
}3.4 异步状态追踪
jsx
function useAsyncState(initialValue) {
const [state, setState] = useState({
value: initialValue,
loading: false,
error: null,
lastUpdated: null
});
const pendingPromises = useRef(new Set());
// 调试信息
useDebugValue(state, (s) => {
const pendingCount = pendingPromises.current.size;
const status = s.loading ? '加载中' : s.error ? '错误' : '正常';
const timeSinceUpdate = s.lastUpdated
? `${Math.round((Date.now() - s.lastUpdated.getTime()) / 1000)}秒前`
: '从未更新';
return `AsyncState(${status}): ${JSON.stringify(s.value)} | ${pendingCount}个待处理 | ${timeSinceUpdate}`;
});
const setAsyncValue = async (asyncFn) => {
const promise = (async () => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const newValue = await asyncFn(state.value);
setState({
value: newValue,
loading: false,
error: null,
lastUpdated: new Date()
});
} catch (error) {
setState(prev => ({
...prev,
loading: false,
error: error.message,
lastUpdated: new Date()
}));
} finally {
pendingPromises.current.delete(promise);
}
})();
pendingPromises.current.add(promise);
return promise;
};
return [state, setAsyncValue];
}
// 使用
function AsyncComponent() {
const [state, setAsyncValue] = useAsyncState(0);
const handleIncrement = async () => {
await setAsyncValue(async (current) => {
await new Promise(resolve => setTimeout(resolve, 1000));
return current + 1;
});
};
return (
<div>
<p>值: {state.value}</p>
<p>状态: {state.loading ? '加载中' : '就绪'}</p>
{state.error && <p>错误: {state.error}</p>}
<button onClick={handleIncrement} disabled={state.loading}>
异步增加
</button>
</div>
);
}第四部分:组合使用
4.1 多个Hook组合调试
jsx
function useComplexFeature(userId) {
const user = useFetch(`/api/users/${userId}`);
const [preferences, setPreferences] = useLocalStorage(`prefs-${userId}`, {});
const online = useOnlineStatus();
const [notifications, setNotifications] = useState([]);
// 组合调试信息
useDebugValue(
{ user, preferences, online, notifications },
(state) => {
const userStatus = state.user.loading ? '加载中' :
state.user.error ? '错误' :
state.user.data ? '已加载' : '未开始';
const prefsCount = Object.keys(state.preferences).length;
const notifCount = state.notifications.length;
return `ComplexFeature: 用户(${userStatus}) | ${prefsCount}个偏好 | ${state.online ? '在线' : '离线'} | ${notifCount}个通知`;
}
);
return {
user: user.data,
loading: user.loading,
error: user.error,
preferences,
setPreferences,
online,
notifications
};
}
// 使用
function ComplexComponent({ userId }) {
const feature = useComplexFeature(userId);
if (feature.loading) return <div>加载中...</div>;
if (feature.error) return <div>错误: {feature.error}</div>;
return (
<div>
<h2>{feature.user?.name}</h2>
<p>在线状态: {feature.online ? '在线' : '离线'}</p>
<p>通知: {feature.notifications.length}条</p>
</div>
);
}4.2 创建调试工具
jsx
function useDebugTools(componentName) {
const renderCount = useRef(0);
const previousProps = useRef(null);
const renderTimestamps = useRef([]);
const stateChanges = useRef([]);
renderCount.current++;
renderTimestamps.current.push(Date.now());
// 工具方法
const logState = (stateName, value) => {
stateChanges.current.push({
name: stateName,
value,
timestamp: Date.now(),
renderCount: renderCount.current
});
};
const logPropsChange = (props) => {
if (previousProps.current) {
const changes = Object.keys(props).filter(
key => !Object.is(props[key], previousProps.current[key])
);
if (changes.length > 0) {
console.log(`[${componentName}] Props changed:`, changes);
}
}
previousProps.current = props;
};
// 综合调试信息
useDebugValue(
{ componentName, renderCount: renderCount.current, stateChanges: stateChanges.current },
(state) => {
const recentChanges = state.stateChanges.slice(-3);
const changesStr = recentChanges
.map(change => `${change.name}=${JSON.stringify(change.value)}`)
.join(', ');
return `${state.componentName}: ${state.renderCount}次渲染 | 最近状态: ${changesStr || '无'}`;
}
);
return {
renderCount: renderCount.current,
logState,
logPropsChange
};
}
// 使用
function DebuggedComponent({ userId, filter }) {
const debug = useDebugTools('DebuggedComponent');
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
debug.logPropsChange({ userId, filter });
useEffect(() => {
debug.logState('count', count);
}, [count]);
useEffect(() => {
debug.logState('items', items);
}, [items]);
return (
<div>
<p>渲染次数: {debug.renderCount}</p>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>增加</button>
</div>
);
}第五部分:TypeScript集成
5.1 类型安全的useDebugValue
typescript
import { useDebugValue, useState } from 'react';
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
}
interface UseUserOptions {
initialUser?: User | null;
autoFetch?: boolean;
}
interface UseUserReturn {
user: User | null;
loading: boolean;
error: string | null;
setUser: (user: User | null) => void;
refetch: () => Promise<void>;
}
function useUser(userId: number, options: UseUserOptions = {}): UseUserReturn {
const [user, setUser] = useState<User | null>(options.initialUser || null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 类型安全的调试值
useDebugValue<User | null>(user, (u) => {
if (!u) return 'No user';
return `User: ${u.name} (${u.role})`;
});
const refetch = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
return { user, loading, error, setUser, refetch };
}5.2 泛型Debug Hook
typescript
interface DebugState<T> {
value: T;
history: T[];
updateCount: number;
lastUpdated: Date | null;
}
function useDebugState<T>(
initialValue: T,
hookName?: string
): [T, (value: T | ((prev: T) => T)) => void] {
const [state, setState] = useState<DebugState<T>>({
value: initialValue,
history: [initialValue],
updateCount: 0,
lastUpdated: null
});
useDebugValue<DebugState<T>>(
state,
(s) => {
const name = hookName || 'State';
const valueStr = JSON.stringify(s.value);
const historySize = s.history.length;
const timeSinceUpdate = s.lastUpdated
? `${Math.round((Date.now() - s.lastUpdated.getTime()) / 1000)}秒前`
: '从未更新';
return `${name}: ${valueStr} | ${s.updateCount}次更新 | ${historySize}条历史 | ${timeSinceUpdate}`;
}
);
const setValue = (newValue: T | ((prev: T) => T)) => {
setState(prevState => {
const actualValue = newValue instanceof Function
? newValue(prevState.value)
: newValue;
return {
value: actualValue,
history: [...prevState.history, actualValue].slice(-10),
updateCount: prevState.updateCount + 1,
lastUpdated: new Date()
};
});
};
return [state.value, setValue];
}
// 使用
function TypedComponent() {
const [count, setCount] = useDebugState<number>(0, 'Counter');
const [user, setUser] = useDebugState<User | null>(null, 'User');
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>增加</button>
{user && <p>User: {user.name}</p>}
</div>
);
}第六部分:生产环境策略
6.1 条件调试
jsx
function useConditionalDebug(value, condition) {
// 只在特定条件下显示调试信息
useDebugValue(
condition ? value : null,
(v) => v ? JSON.stringify(v) : '调试已禁用'
);
return value;
}
// 使用
function Component() {
const isDev = process.env.NODE_ENV === 'development';
const isDebugMode = window.location.search.includes('debug=true');
const data = useConditionalDebug(
{ count: 100, items: [] },
isDev || isDebugMode
);
return <div>{/* ... */}</div>;
}6.2 环境感知调试
jsx
const DEBUG_ENABLED = process.env.NODE_ENV === 'development';
function useSmartDebug(value, label) {
if (DEBUG_ENABLED) {
useDebugValue(value, (v) => {
return `${label}: ${JSON.stringify(v, null, 2)}`;
});
}
return value;
}
// 使用
function Component() {
const [count, setCount] = useState(0);
// 在生产环境中不会添加调试信息
useSmartDebug(count, 'Count');
return <div>Count: {count}</div>;
}注意事项
1. 只在自定义Hook中使用
jsx
// ❌ 错误:在组件中使用
function Component() {
const [count, setCount] = useState(0);
useDebugValue(count); // 错误!不会显示
return <div>{count}</div>;
}
// ✅ 正确:在自定义Hook中使用
function useCounter() {
const [count, setCount] = useState(0);
useDebugValue(count); // 正确!
return [count, setCount];
}
function Component() {
const [count, setCount] = useCounter();
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}2. 使用格式化函数优化性能
jsx
// ❌ 不好:每次渲染都计算
function useBadPerf() {
const [data, setData] = useState([]);
// 即使DevTools未打开,也会每次计算
const summary = `${data.length} items, ${data.reduce((sum, item) => sum + item.value, 0)} total`;
useDebugValue(summary);
return [data, setData];
}
// ✅ 好:使用格式化函数
function useGoodPerf() {
const [data, setData] = useState([]);
// 只在DevTools打开时才计算
useDebugValue(data, (d) => {
return `${d.length} items, ${d.reduce((sum, item) => sum + item.value, 0)} total`;
});
return [data, setData];
}3. 提供有意义的信息
jsx
// ❌ 不好:信息不明确
function usePoorDebug() {
const [state, setState] = useState({ a: 1, b: 2 });
useDebugValue(state); // 只显示对象引用
return [state, setState];
}
// ✅ 好:清晰的描述
function useGoodDebug() {
const [state, setState] = useState({ a: 1, b: 2 });
useDebugValue(state, (s) => {
return `a=${s.a}, b=${s.b}, sum=${s.a + s.b}`;
});
return [state, setState];
}4. 避免副作用
jsx
// ❌ 错误:在格式化函数中修改状态
function useBadSideEffect() {
const [count, setCount] = useState(0);
useDebugValue(count, (c) => {
setCount(c + 1); // ❌ 副作用!会导致无限循环
return `Count: ${c}`;
});
return [count, setCount];
}
// ✅ 正确:只读取和格式化
function useGoodPure() {
const [count, setCount] = useState(0);
useDebugValue(count, (c) => {
return `Count: ${c} (${c % 2 === 0 ? 'even' : 'odd'})`;
});
return [count, setCount];
}5. 生产环境的影响
jsx
// useDebugValue在生产环境中不执行
// 但最好还是避免昂贵的计算
// ⚠️ 不推荐:即使在生产环境也会计算
function useExpensiveAlways() {
const [data, setData] = useState([]);
const summary = expensiveCalculation(data); // 总是执行
useDebugValue(summary);
return [data, setData];
}
// ✅ 推荐:延迟计算
function useExpensiveLazy() {
const [data, setData] = useState([]);
useDebugValue(data, expensiveCalculation); // 只在需要时执行
return [data, setData];
}
function expensiveCalculation(data) {
// 耗时计算
return `Processed: ${data.length} items`;
}常见问题
1. 为什么我的useDebugValue不显示?
useDebugValue只在自定义Hook中有效:
jsx
// ❌ 不会显示
function Component() {
const [value, setValue] = useState(0);
useDebugValue(value); // 无效
return <div>{value}</div>;
}
// ✅ 会显示
function useValue() {
const [value, setValue] = useState(0);
useDebugValue(value); // 有效
return [value, setValue];
}
function Component() {
const [value, setValue] = useValue();
return <div>{value}</div>;
}2. 如何调试多个相关状态?
传递对象并使用格式化函数:
jsx
function useMultiState() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [enabled, setEnabled] = useState(true);
useDebugValue(
{ count, name, enabled },
(state) => {
return `count=${state.count}, name="${state.name}", enabled=${state.enabled}`;
}
);
return { count, name, enabled, setCount, setName, setEnabled };
}3. useDebugValue会影响性能吗?
在生产环境中不会,但开发环境中要注意:
jsx
// 使用格式化函数避免不必要的计算
function usePerformant() {
const [data, setData] = useState([]);
useDebugValue(data, (d) => {
// 只在DevTools打开时执行
return `${d.length} items`;
});
return [data, setData];
}4. 如何在TypeScript中使用?
提供正确的类型参数:
typescript
function useTypedDebug<T>(value: T, name: string) {
useDebugValue<T>(value, (v) => {
return `${name}: ${JSON.stringify(v)}`;
});
return value;
}5. 可以嵌套使用吗?
可以,但每个Hook应该有自己的useDebugValue:
jsx
function useInner() {
const [value, setValue] = useState(0);
useDebugValue(value, (v) => `Inner: ${v}`);
return [value, setValue];
}
function useOuter() {
const [inner, setInner] = useInner();
const [outer, setOuter] = useState('');
useDebugValue({ inner, outer }, (state) => {
return `Outer: inner=${state.inner}, outer="${state.outer}"`;
});
return { inner, setInner, outer, setOuter };
}6. 如何在React DevTools中查看?
- 打开React DevTools
- 选择使用自定义Hook的组件
- 查看组件的"hooks"标签
- 自定义Hook会显示useDebugValue的值
总结
useDebugValue核心要点
主要用途
- 在React DevTools中显示自定义Hook的调试信息
- 帮助理解Hook的内部状态
- 优化开发体验
使用场景
- 只在自定义Hook中使用
- 显示Hook的关键状态
- 提供有意义的调试信息
- 性能监控
- 状态历史追踪
性能优化
- 使用格式化函数延迟计算
- 避免昂贵的操作
- 只在必要时显示详细信息
最佳实践
- 提供清晰、有意义的标签
- 使用格式化函数优化性能
- 避免在格式化函数中产生副作用
- 组合多个状态显示
- 考虑生产环境的影响
常见模式
- 状态摘要
- 性能监控
- 历史追踪
- 依赖变化检测
- 异步状态追踪
注意事项
- 只在自定义Hook中有效
- 格式化函数应该是纯函数
- 生产环境中不执行
- 不影响组件行为
与DevTools配合
- 实时查看Hook状态
- 理解组件重渲染原因
- 追踪状态变化
- 性能分析
通过本章学习,你已经全面掌握了useDebugValue的使用。虽然它是一个简单的Hook,但在调试复杂的自定义Hook时非常有用。记住:好的调试信息能大大提高开发效率,使用useDebugValue让你的自定义Hook更易于维护和理解!