Appearance
网络请求调试 - React应用HTTP调试完全指南
1. 浏览器开发者工具
1.1 Network面板基础
typescript
const networkPanel = {
基本功能: {
请求列表: '显示所有HTTP请求',
请求详情: '查看请求头、响应头、payload',
时间线: '请求时序图',
过滤器: '按类型、域名、状态码过滤',
搜索: '搜索请求内容'
},
请求信息: {
General: '请求方法、状态码、URL',
Headers: '请求头和响应头',
Payload: '请求体',
Preview: '响应预览',
Response: '原始响应',
Timing: '请求各阶段耗时'
},
快捷操作: {
清空: 'Ctrl + E',
保存HAR: '导出网络日志',
过滤: '按类型/域名过滤',
复制: '复制为cURL/Fetch'
}
};1.2 查看请求详情
typescript
// 典型的API请求分析
const requestAnalysis = {
基本信息: {
URL: 'https://api.example.com/users/123',
Method: 'GET',
Status: '200 OK',
Type: 'xhr',
Size: '2.1 KB',
Time: '125 ms'
},
请求头: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token...',
'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0...'
},
响应头: {
'Content-Type': 'application/json',
'Cache-Control': 'max-age=3600',
'Access-Control-Allow-Origin': '*',
'X-Response-Time': '15ms'
},
响应体: {
id: 123,
name: 'John Doe',
email: 'john@example.com'
}
};2. React中的网络请求调试
2.1 Fetch API调试
typescript
// 在组件中调试fetch请求
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchUser() {
try {
console.log('🔍 Fetching user:', userId);
// 在Network面板查看此请求
const response = await fetch(`/api/users/${userId}`);
console.log('📥 Response status:', response.status);
console.log('📥 Response headers:', [...response.headers]);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('✅ Data received:', data);
setUser(data);
} catch (err) {
console.error('❌ Fetch error:', err);
setError(err.message);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}2.2 Axios调试
typescript
import axios from 'axios';
// 配置Axios拦截器用于调试
const setupAxiosDebug = () => {
// 请求拦截器
axios.interceptors.request.use(
config => {
console.group('🚀 Axios Request');
console.log('URL:', config.url);
console.log('Method:', config.method);
console.log('Headers:', config.headers);
console.log('Data:', config.data);
console.log('Params:', config.params);
console.groupEnd();
// 可以在这里修改请求
config.metadata = { startTime: new Date() };
return config;
},
error => {
console.error('❌ Request Error:', error);
return Promise.reject(error);
}
);
// 响应拦截器
axios.interceptors.response.use(
response => {
const endTime = new Date();
const duration = endTime - response.config.metadata.startTime;
console.group('✅ Axios Response');
console.log('URL:', response.config.url);
console.log('Status:', response.status);
console.log('Duration:', `${duration}ms`);
console.log('Headers:', response.headers);
console.log('Data:', response.data);
console.groupEnd();
return response;
},
error => {
console.group('❌ Axios Error');
console.log('URL:', error.config?.url);
console.log('Status:', error.response?.status);
console.log('Message:', error.message);
console.log('Response:', error.response?.data);
console.groupEnd();
return Promise.reject(error);
}
);
};
// 使用
setupAxiosDebug();
// 组件中使用
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
axios.get('/api/data')
.then(response => {
setData(response.data);
})
.catch(error => {
console.error('Failed to fetch:', error);
});
}, []);
return <div>{data && JSON.stringify(data)}</div>;
}2.3 React Query调试
typescript
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
// 配置QueryClient用于调试
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// 开发环境启用详细日志
onError: (error) => {
console.error('Query Error:', error);
},
onSuccess: (data) => {
console.log('Query Success:', data);
},
// 启用重试日志
retry: (failureCount, error) => {
console.log(`Retry attempt ${failureCount}:`, error);
return failureCount < 3;
}
}
},
// 配置logger
logger: {
log: (...args) => console.log('[React Query]', ...args),
warn: (...args) => console.warn('[React Query]', ...args),
error: (...args) => console.error('[React Query]', ...args)
}
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
{/* React Query开发工具 */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
// 使用useQuery
function Users() {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['users'],
queryFn: async () => {
console.log('🔄 Fetching users...');
const response = await fetch('/api/users');
if (!response.ok) {
console.error('❌ Response not ok:', response.status);
throw new Error('Failed to fetch users');
}
const data = await response.json();
console.log('✅ Users fetched:', data.length, 'users');
return data;
},
// 启用详细日志
meta: {
errorMessage: 'Failed to load users'
}
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<button onClick={() => refetch()}>Refresh</button>
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}3. 网络请求Mock
3.1 MSW (Mock Service Worker)
typescript
// src/mocks/handlers.ts
import { rest } from 'msw';
export const handlers = [
// GET请求mock
rest.get('/api/users', (req, res, ctx) => {
console.log('🎭 MSW: Intercepted GET /api/users');
return res(
ctx.status(200),
ctx.json([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
])
);
}),
// POST请求mock
rest.post('/api/users', async (req, res, ctx) => {
const body = await req.json();
console.log('🎭 MSW: Intercepted POST /api/users', body);
return res(
ctx.status(201),
ctx.json({
id: 3,
...body
})
);
}),
// 模拟错误
rest.get('/api/error', (req, res, ctx) => {
console.log('🎭 MSW: Simulating error');
return res(
ctx.status(500),
ctx.json({
error: 'Internal Server Error'
})
);
}),
// 模拟延迟
rest.get('/api/slow', (req, res, ctx) => {
console.log('🎭 MSW: Simulating slow response');
return res(
ctx.delay(3000), // 3秒延迟
ctx.json({ message: 'Slow response' })
);
})
];
// src/mocks/browser.ts
import { setupWorker } from 'msw';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
// src/index.tsx
if (process.env.NODE_ENV === 'development') {
const { worker } = await import('./mocks/browser');
worker.start({
onUnhandledRequest: 'warn' // 警告未处理的请求
});
}3.2 自定义Mock工具
typescript
// mockApi.ts
class MockAPI {
private mocks = new Map();
private delays = new Map();
// 注册mock响应
mock(url, response, options = {}) {
console.log(`📝 Mocking: ${url}`);
this.mocks.set(url, {
response,
status: options.status || 200,
delay: options.delay || 0
});
}
// 拦截fetch
setupFetchMock() {
const originalFetch = window.fetch;
window.fetch = async (url, options) => {
const urlString = typeof url === 'string' ? url : url.toString();
// 检查是否有mock
if (this.mocks.has(urlString)) {
const mock = this.mocks.get(urlString);
console.group('🎭 Mock Response');
console.log('URL:', urlString);
console.log('Method:', options?.method || 'GET');
console.log('Delay:', mock.delay, 'ms');
console.log('Response:', mock.response);
console.groupEnd();
// 模拟延迟
if (mock.delay > 0) {
await new Promise(resolve => setTimeout(resolve, mock.delay));
}
// 返回mock响应
return Promise.resolve({
ok: mock.status >= 200 && mock.status < 300,
status: mock.status,
json: async () => mock.response,
text: async () => JSON.stringify(mock.response),
headers: new Headers({
'Content-Type': 'application/json'
})
});
}
// 没有mock,使用真实请求
console.log('🌐 Real fetch:', urlString);
return originalFetch(url, options);
};
}
// 清除所有mock
clearAll() {
console.log('🧹 Clearing all mocks');
this.mocks.clear();
}
}
// 使用
const mockAPI = new MockAPI();
mockAPI.setupFetchMock();
// 注册mock
mockAPI.mock('/api/users', [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
], { delay: 500 });
mockAPI.mock('/api/error',
{ error: 'Not found' },
{ status: 404 }
);4. 代理配置
4.1 Vite代理配置
javascript
// vite.config.js
export default {
server: {
proxy: {
// 代理所有/api请求
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
// 配置代理日志
configure: (proxy, options) => {
proxy.on('error', (err, req, res) => {
console.log('❌ Proxy error:', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('🔄 Proxying:', req.method, req.url);
});
proxy.on('proxyRes', (proxyRes, req, res) => {
console.log('✅ Proxy response:', proxyRes.statusCode, req.url);
});
}
},
// WebSocket代理
'/ws': {
target: 'ws://localhost:3001',
ws: true
}
}
}
};4.2 Next.js代理配置
javascript
// next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:3001/:path*'
}
];
}
};4.3 CRA代理配置
javascript
// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:3001',
changeOrigin: true,
pathRewrite: {
'^/api': ''
},
onProxyReq: (proxyReq, req, res) => {
console.log('🔄 Proxying:', req.method, req.url);
},
onProxyRes: (proxyRes, req, res) => {
console.log('✅ Proxy response:', proxyRes.statusCode);
},
onError: (err, req, res) => {
console.error('❌ Proxy error:', err);
}
})
);
};5. 请求性能分析
5.1 Timing分析
typescript
// 详细的请求时序分析
const requestTiming = {
Queueing: '请求在队列中等待的时间',
Stalled: '在发送前的停滞时间',
DNSLookup: 'DNS查询时间',
InitialConnection: '建立TCP连接时间',
SSL: 'SSL握手时间',
RequestSent: '发送请求时间',
WaitingTTFB: '等待首字节响应时间',
ContentDownload: '下载响应内容时间'
};
// 使用Performance API测量
function measureRequest(url) {
const startTime = performance.now();
fetch(url)
.then(response => {
const endTime = performance.now();
const duration = endTime - startTime;
console.group('⏱️ Request Performance');
console.log('URL:', url);
console.log('Total Time:', `${duration.toFixed(2)}ms`);
console.log('Status:', response.status);
console.log('Size:', response.headers.get('content-length'), 'bytes');
console.groupEnd();
return response.json();
});
}
// 使用Resource Timing API
function analyzeResourceTiming(url) {
const entries = performance.getEntriesByType('resource');
const entry = entries.find(e => e.name.includes(url));
if (entry) {
console.group('📊 Resource Timing');
console.log('DNS Lookup:', `${entry.domainLookupEnd - entry.domainLookupStart}ms`);
console.log('TCP Connect:', `${entry.connectEnd - entry.connectStart}ms`);
console.log('Request:', `${entry.responseStart - entry.requestStart}ms`);
console.log('Response:', `${entry.responseEnd - entry.responseStart}ms`);
console.log('Total:', `${entry.duration}ms`);
console.groupEnd();
}
}5.2 请求缓存分析
typescript
// 分析缓存命中情况
function analyzeCaching() {
const entries = performance.getEntriesByType('resource');
const cacheStats = {
fromCache: 0,
fromNetwork: 0,
total: entries.length
};
entries.forEach(entry => {
// 检查是否从缓存加载
if (entry.transferSize === 0) {
cacheStats.fromCache++;
console.log('💾 From cache:', entry.name);
} else {
cacheStats.fromNetwork++;
console.log('🌐 From network:', entry.name, `(${entry.transferSize} bytes)`);
}
});
console.group('📈 Cache Statistics');
console.log('Total Requests:', cacheStats.total);
console.log('From Cache:', cacheStats.fromCache);
console.log('From Network:', cacheStats.fromNetwork);
console.log('Cache Hit Rate:', `${(cacheStats.fromCache / cacheStats.total * 100).toFixed(2)}%`);
console.groupEnd();
}6. WebSocket调试
6.1 WebSocket连接调试
typescript
class DebugWebSocket extends WebSocket {
constructor(url, protocols) {
super(url, protocols);
console.log('🔌 WebSocket: Connecting to', url);
this.addEventListener('open', (event) => {
console.log('✅ WebSocket: Connected');
});
this.addEventListener('message', (event) => {
console.group('📨 WebSocket: Message Received');
console.log('Data:', event.data);
console.log('Time:', new Date().toISOString());
try {
const parsed = JSON.parse(event.data);
console.log('Parsed:', parsed);
} catch (e) {
// 不是JSON数据
}
console.groupEnd();
});
this.addEventListener('error', (event) => {
console.error('❌ WebSocket: Error', event);
});
this.addEventListener('close', (event) => {
console.log('🔌 WebSocket: Closed', {
code: event.code,
reason: event.reason,
wasClean: event.wasClean
});
});
}
send(data) {
console.group('📤 WebSocket: Sending');
console.log('Data:', data);
try {
const parsed = JSON.parse(data);
console.log('Parsed:', parsed);
} catch (e) {
// 不是JSON数据
}
console.groupEnd();
super.send(data);
}
}
// 使用
function ChatComponent() {
const [ws, setWs] = useState(null);
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new DebugWebSocket('ws://localhost:8080');
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};
setWs(socket);
return () => {
socket.close();
};
}, []);
const sendMessage = (text) => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'message', text }));
}
};
return (
<div>
{messages.map((msg, i) => (
<div key={i}>{msg.text}</div>
))}
<button onClick={() => sendMessage('Hello')}>Send</button>
</div>
);
}6.2 Socket.io调试
typescript
import io from 'socket.io-client';
// 启用调试模式
const socket = io('http://localhost:3001', {
// 启用日志
debug: true,
// 自定义日志函数
logger: {
log: (...args) => console.log('[Socket.io]', ...args),
error: (...args) => console.error('[Socket.io]', ...args)
}
});
// 监听所有事件
function logAllEvents(socket) {
const onevent = socket.onevent;
socket.onevent = function(packet) {
console.group('📡 Socket.io Event');
console.log('Event:', packet.data[0]);
console.log('Data:', packet.data.slice(1));
console.log('Time:', new Date().toISOString());
console.groupEnd();
onevent.call(this, packet);
};
}
logAllEvents(socket);
// 监听连接事件
socket.on('connect', () => {
console.log('✅ Socket.io: Connected', socket.id);
});
socket.on('disconnect', (reason) => {
console.log('🔌 Socket.io: Disconnected', reason);
});
socket.on('error', (error) => {
console.error('❌ Socket.io: Error', error);
});7. GraphQL请求调试
7.1 Apollo Client调试
typescript
import { ApolloClient, InMemoryCache, ApolloLink } from '@apollo/client';
// 创建日志链接
const logLink = new ApolloLink((operation, forward) => {
console.group('🚀 GraphQL Request');
console.log('Operation:', operation.operationName);
console.log('Variables:', operation.variables);
console.log('Query:', operation.query.loc.source.body);
console.groupEnd();
const startTime = Date.now();
return forward(operation).map(response => {
const duration = Date.now() - startTime;
console.group('✅ GraphQL Response');
console.log('Operation:', operation.operationName);
console.log('Duration:', `${duration}ms`);
console.log('Data:', response.data);
if (response.errors) {
console.error('Errors:', response.errors);
}
console.groupEnd();
return response;
});
});
// 创建Apollo Client
const client = new ApolloClient({
uri: '/graphql',
cache: new InMemoryCache(),
link: logLink.concat(httpLink),
// 开发工具
connectToDevTools: true
});
// 使用
function Users() {
const { data, loading, error } = useQuery(GET_USERS);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data.users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}7.2 GraphQL Playground
typescript
// 在开发环境启用GraphQL Playground
const enableGraphQLPlayground = () => {
if (process.env.NODE_ENV === 'development') {
console.log('🎮 GraphQL Playground available at /graphql');
}
};8. 错误处理和调试
8.1 网络错误处理
typescript
// 统一错误处理
async function fetchWithErrorHandling(url, options = {}) {
try {
console.log('🔄 Fetching:', url);
const response = await fetch(url, options);
// 检查HTTP状态
if (!response.ok) {
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
error.response = response;
error.status = response.status;
// 尝试解析错误响应
try {
error.data = await response.json();
} catch (e) {
error.data = await response.text();
}
console.error('❌ HTTP Error:', {
url,
status: error.status,
data: error.data
});
throw error;
}
const data = await response.json();
console.log('✅ Success:', url, data);
return data;
} catch (error) {
// 网络错误
if (error.name === 'TypeError' && error.message.includes('fetch')) {
console.error('❌ Network Error:', {
url,
message: 'Network request failed',
originalError: error
});
error.isNetworkError = true;
}
// 超时错误
if (error.name === 'AbortError') {
console.error('⏱️ Timeout Error:', url);
error.isTimeout = true;
}
throw error;
}
}
// 使用
function DataFetcher() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetchWithErrorHandling('/api/data')
.then(setData)
.catch(error => {
setError(error);
// 根据错误类型显示不同信息
if (error.isNetworkError) {
alert('网络连接失败,请检查网络');
} else if (error.isTimeout) {
alert('请求超时,请稍后重试');
} else if (error.status === 404) {
alert('资源不存在');
} else if (error.status >= 500) {
alert('服务器错误,请稍后重试');
}
});
}, []);
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>Loading...</div>;
return <div>{JSON.stringify(data)}</div>;
}8.2 重试机制
typescript
// 带重试的fetch
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let i = 0; i <= maxRetries; i++) {
try {
if (i > 0) {
console.log(`🔄 Retry attempt ${i}/${maxRetries}:`, url);
// 指数退避
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
lastError = error;
console.error(`❌ Attempt ${i + 1} failed:`, error.message);
// 最后一次尝试失败,抛出错误
if (i === maxRetries) {
console.error(`❌ All ${maxRetries + 1} attempts failed`);
throw lastError;
}
}
}
}
// 使用
function RobustDataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetchWithRetry('/api/data', {}, 3)
.then(setData)
.catch(error => {
console.error('Final error:', error);
});
}, []);
return data ? <div>{JSON.stringify(data)}</div> : <div>Loading...</div>;
}9. 调试工具推荐
9.1 浏览器扩展
typescript
const browserExtensions = {
通用工具: [
'React Developer Tools - 查看组件树和props',
'Redux DevTools - Redux状态管理调试',
'Apollo Client Devtools - GraphQL调试'
],
网络工具: [
'ModHeader - 修改请求头',
'Requestly - 拦截和修改请求',
'JSON Formatter - 格式化JSON响应'
],
性能工具: [
'Lighthouse - 性能分析',
'Web Vitals - 核心指标监控'
]
};9.2 独立工具
typescript
const standaloneTools = {
Postman: {
功能: ['API测试', '请求集合', '环境变量', '自动化测试'],
优势: '功能强大,团队协作'
},
Insomnia: {
功能: ['REST API', 'GraphQL', 'gRPC', 'WebSocket'],
优势: '简洁易用,支持多种协议'
},
Charles: {
功能: ['代理调试', 'SSL抓包', '请求修改', '限速模拟'],
优势: '专业的网络调试工具'
},
Fiddler: {
功能: ['HTTP调试', '性能分析', '安全测试'],
优势: 'Windows平台首选'
}
};10. 调试最佳实践
10.1 调试检查清单
typescript
const debuggingChecklist = [
'✅ 检查请求URL是否正确',
'✅ 检查请求方法(GET/POST等)',
'✅ 检查请求头(Content-Type, Authorization等)',
'✅ 检查请求体格式',
'✅ 检查响应状态码',
'✅ 检查响应头(CORS等)',
'✅ 检查响应数据格式',
'✅ 检查网络连接',
'✅ 检查代理配置',
'✅ 检查HTTPS证书',
'✅ 检查请求超时',
'✅ 检查并发请求数',
'✅ 检查缓存策略',
'✅ 检查错误处理'
];10.2 性能优化建议
typescript
const performanceOptimization = {
减少请求数: [
'合并API请求',
'使用GraphQL',
'批量操作',
'按需加载'
],
优化请求大小: [
'启用gzip压缩',
'只请求需要的字段',
'分页加载',
'图片压缩'
],
利用缓存: [
'HTTP缓存头',
'Service Worker',
'本地存储',
'CDN缓存'
],
并发控制: [
'限制并发数',
'请求队列',
'请求优先级',
'取消重复请求'
]
};11. 总结
网络请求调试的核心要点:
- 浏览器开发者工具: Network面板是最基础的调试工具
- 请求拦截: 使用拦截器记录和修改请求
- Mock数据: 使用MSW等工具模拟API
- 代理配置: 解决跨域和环境切换问题
- 性能分析: 关注请求时序和缓存
- 错误处理: 完善的错误处理和重试机制
- 调试工具: 善用浏览器扩展和独立工具
- 最佳实践: 遵循调试检查清单
掌握这些网络请求调试技巧,可以快速定位和解决网络相关问题,提升开发效率。