在 React 项目中使用 axios 时,需要考虑组件生命周期、状态管理、性能优化等多个方面。
1. 基础封装
创建 API 服务层
javascript// api/client.js import axios from 'axios'; const apiClient = axios.create({ baseURL: process.env.REACT_APP_API_URL, timeout: 10000, headers: { 'Content-Type': 'application/json' } }); // 请求拦截器 apiClient.interceptors.request.use( (config) => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // 响应拦截器 apiClient.interceptors.response.use( (response) => response.data, (error) => { if (error.response?.status === 401) { localStorage.removeItem('token'); window.location.href = '/login'; } return Promise.reject(error); } ); export default apiClient;
按模块组织 API
javascript// api/userApi.js import apiClient from './client'; export const userApi = { getProfile: () => apiClient.get('/users/profile'), updateProfile: (data) => apiClient.put('/users/profile', data), uploadAvatar: (formData) => apiClient.post('/users/avatar', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) }; // api/postApi.js export const postApi = { getList: (params) => apiClient.get('/posts', { params }), getDetail: (id) => apiClient.get(`/posts/${id}`), create: (data) => apiClient.post('/posts', data), update: (id, data) => apiClient.put(`/posts/${id}`, data), delete: (id) => apiClient.delete(`/posts/${id}`) };
2. 在组件中使用
使用 useEffect 和 AbortController
javascriptimport { useEffect, useState } from 'react'; import { userApi } from '../api/userApi'; function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const controller = new AbortController(); const fetchUser = async () => { try { setLoading(true); const data = await userApi.getProfile(userId, { signal: controller.signal }); setUser(data); } catch (err) { if (!axios.isCancel(err)) { setError(err.message); } } finally { setLoading(false); } }; fetchUser(); // 清理函数:组件卸载时取消请求 return () => { controller.abort(); }; }, [userId]); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> ); }
自定义 Hook 封装
javascript// hooks/useApi.js import { useState, useEffect, useCallback } from 'react'; import axios from 'axios'; export const useApi = (apiFunction, dependencies = []) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const execute = useCallback(async (...params) => { const controller = new AbortController(); try { setLoading(true); setError(null); const result = await apiFunction(...params, { signal: controller.signal }); setData(result); return result; } catch (err) { if (!axios.isCancel(err)) { setError(err); throw err; } } finally { setLoading(false); } return () => controller.abort(); }, dependencies); return { data, loading, error, execute }; }; // 使用 function PostList() { const { data: posts, loading, error, execute: fetchPosts } = useApi(postApi.getList); useEffect(() => { fetchPosts(); }, [fetchPosts]); if (loading) return <Spinner />; if (error) return <ErrorMessage error={error} />; return ( <ul> {posts?.map(post => <PostItem key={post.id} post={post} />)} </ul> ); }
3. 常见坑点及解决方案
坑点 1:内存泄漏警告
javascript// ❌ 错误示例:组件卸载后设置状态 useEffect(() => { fetchUser().then(data => { setUser(data); // 组件卸载后可能报错 }); }, []); // ✅ 正确做法:使用 AbortController 或标记位 useEffect(() => { let isMounted = true; const controller = new AbortController(); fetchUser({ signal: controller.signal }) .then(data => { if (isMounted) { setUser(data); } }); return () => { isMounted = false; controller.abort(); }; }, []);
坑点 2:竞态条件
javascript// ❌ 错误示例:快速切换时显示旧数据 useEffect(() => { fetchUser(userId).then(data => { setUser(data); // 可能显示之前请求的结果 }); }, [userId]); // ✅ 正确做法:取消之前的请求 useEffect(() => { const controller = new AbortController(); fetchUser(userId, { signal: controller.signal }) .then(data => setUser(data)) .catch(err => { if (!axios.isCancel(err)) { setError(err); } }); return () => controller.abort(); }, [userId]);
坑点 3:表单重复提交
javascript// ❌ 错误示例:重复点击提交 const handleSubmit = async (values) => { await createPost(values); // 可以重复点击 }; // ✅ 正确做法:使用 loading 状态防止重复提交 const [submitting, setSubmitting] = useState(false); const handleSubmit = async (values) => { if (submitting) return; setSubmitting(true); try { await createPost(values); message.success('创建成功'); } catch (error) { message.error(error.message); } finally { setSubmitting(false); } };
坑点 4:错误边界处理
javascript// ErrorBoundary.js import React from 'react'; class ApiErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { console.error('API Error:', error, errorInfo); } render() { if (this.state.hasError) { return <ErrorFallback error={this.state.error} />; } return this.props.children; } }
4. 与状态管理结合
使用 Context + useReducer
javascript// contexts/ApiContext.js const ApiContext = createContext(); const initialState = { users: [], loading: false, error: null }; function apiReducer(state, action) { switch (action.type) { case 'FETCH_START': return { ...state, loading: true, error: null }; case 'FETCH_SUCCESS': return { ...state, loading: false, users: action.payload }; case 'FETCH_ERROR': return { ...state, loading: false, error: action.payload }; default: return state; } } export function ApiProvider({ children }) { const [state, dispatch] = useReducer(apiReducer, initialState); const fetchUsers = useCallback(async () => { dispatch({ type: 'FETCH_START' }); try { const users = await userApi.getList(); dispatch({ type: 'FETCH_SUCCESS', payload: users }); } catch (error) { dispatch({ type: 'FETCH_ERROR', payload: error.message }); } }, []); return ( <ApiContext.Provider value={{ ...state, fetchUsers }}> {children} </ApiContext.Provider> ); }
使用 React Query(推荐)
javascriptimport { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; // 获取数据 function useUsers() { return useQuery({ queryKey: ['users'], queryFn: () => userApi.getList(), staleTime: 5 * 60 * 1000, // 5分钟缓存 }); } // 修改数据 function useCreateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: userApi.create, onSuccess: () => { // 成功后刷新用户列表 queryClient.invalidateQueries({ queryKey: ['users'] }); } }); } // 组件中使用 function UserManager() { const { data: users, isLoading } = useUsers(); const createUser = useCreateUser(); const handleCreate = async (values) => { await createUser.mutateAsync(values); }; if (isLoading) return <Spinner />; return ( <div> <UserList users={users} /> <CreateUserForm onSubmit={handleCreate} /> </div> ); }
5. 性能优化
请求去重
javascript// hooks/useDedupedApi.js const pendingRequests = new Map(); export const useDedupedApi = (apiFunction) => { return useCallback(async (...params) => { const key = JSON.stringify({ func: apiFunction.name, params }); if (pendingRequests.has(key)) { return pendingRequests.get(key); } const promise = apiFunction(...params).finally(() => { pendingRequests.delete(key); }); pendingRequests.set(key, promise); return promise; }, [apiFunction]); };
乐观更新
javascriptconst useOptimisticUpdate = () => { const queryClient = useQueryClient(); const updateOptimistically = useCallback(async ({ queryKey, mutationFn, updateFn, rollbackOnError = true }) => { // 取消正在进行的重新获取 await queryClient.cancelQueries({ queryKey }); // 保存之前的状态 const previousData = queryClient.getQueryData(queryKey); // 乐观更新 queryClient.setQueryData(queryKey, updateFn); try { await mutationFn(); } catch (error) { // 出错时回滚 if (rollbackOnError) { queryClient.setQueryData(queryKey, previousData); } throw error; } }, [queryClient]); return { updateOptimistically }; };
最佳实践总结
- 封装 API 层:统一处理配置、拦截器、错误处理
- 使用 AbortController:组件卸载时取消请求,防止内存泄漏
- 自定义 Hooks:复用请求逻辑,统一管理 loading/error 状态
- 防止重复提交:使用 loading 状态或防抖处理
- 考虑使用 React Query:自动处理缓存、重试、乐观更新等
- 错误边界:使用 ErrorBoundary 捕获渲染错误
- 竞态处理:确保只显示最新请求的结果