当前位置: 首页 > news >正文

04-性能优化与最佳实践——11. 数据获取 - fetch 与 axios

11. 数据获取 - fetch 与 axios

概述

数据获取是现代 React 应用的核心需求。fetch是浏览器原生 API,axios是第三方 HTTP 客户端库。两者都能发送 HTTP 请求,但在功能、语法和易用性上有所不同。

维度内容
What使用 fetch API 或 axios 库从服务器获取数据
Why从后端 API 获取数据,动态更新 UI
When组件需要从服务器加载数据时
WhereuseEffect 钩子中、事件处理函数中
Who需要与服务端交互的开发者
Howfetch(url).then(res => res.json())axios.get(url)

1. fetch vs axios 对比

1.1 快速对比表

特性fetchaxios
浏览器支持原生,需要 polyfill 支持 IE所有浏览器(包括 IE)
语法基于 Promise基于 Promise
JSON 处理需要手动.json()自动转换
请求拦截不支持原生支持
响应拦截不支持原生支持
取消请求使用 AbortController支持 CancelToken
超时设置不支持原生支持
上传进度需要手动实现支持
体积0KB(原生)~13KB(压缩后)
错误处理只捕获网络错误捕获 HTTP 错误状态码

1.2 基础语法对比

// fetch 版本 fetch('https://api.example.com/users') .then(response => { if (!response.ok) { throw new Error('HTTP error ' + response.status); } return response.json(); }) .then(data => console.log(data)) .catch(error => console.error(error)); // axios 版本 axios.get('https://api.example.com/users') .then(response => console.log(response.data)) .catch(error => console.error(error));

2. fetch API 详解

2.1 基础 GET 请求

import { useState, useEffect } from 'react'; function FetchUsers() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetch('https://jsonplaceholder.typicode.com/users') .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { setUsers(data); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, []); if (loading) return <div>加载中...</div>; if (error) return <div>错误: {error}</div>; return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }

2.2 POST 请求

function CreatePost() { const [title, setTitle] = useState(''); const [body, setBody] = useState(''); const [status, setStatus] = useState('idle'); const handleSubmit = async (e) => { e.preventDefault(); setStatus('loading'); try { const response = await fetch('https://jsonplaceholder.typicode.com/posts', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title, body, userId: 1, }), }); if (!response.ok) { throw new Error('创建失败'); } const data = await response.json(); console.log('创建成功:', data); setStatus('success'); setTitle(''); setBody(''); } catch (error) { console.error('错误:', error); setStatus('error'); } }; return ( <form onSubmit={handleSubmit}> <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="标题" /> <textarea value={body} onChange={(e) => setBody(e.target.value)} placeholder="内容" /> <button type="submit" disabled={status === 'loading'}> {status === 'loading' ? '提交中...' : '提交'} </button> </form> ); }

2.3 PUT 和 DELETE 请求

// PUT 更新 async function updateUser(id, userData) { const response = await fetch(`https://api.example.com/users/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(userData), }); if (!response.ok) { throw new Error('更新失败'); } return response.json(); } // DELETE 删除 async function deleteUser(id) { const response = await fetch(`https://api.example.com/users/${id}`, { method: 'DELETE', }); if (!response.ok) { throw new Error('删除失败'); } return true; }

2.4 带参数的 GET 请求

// 查询参数 function SearchUsers({ query }) { const [results, setResults] = useState([]); useEffect(() => { const url = new URL('https://api.example.com/users'); url.searchParams.append('q', query); url.searchParams.append('limit', '10'); fetch(url) .then(res => res.json()) .then(data => setResults(data)); }, [query]); return <UserList users={results} />; }

2.5 请求取消(AbortController)

function CancelableFetch() { const [data, setData] = useState(null); useEffect(() => { const abortController = new AbortController(); fetch('https://api.example.com/slow-endpoint', { signal: abortController.signal, }) .then(res => res.json()) .then(data => setData(data)) .catch(err => { if (err.name === 'AbortError') { console.log('请求已取消'); } else { console.error('错误:', err); } }); // 组件卸载时取消请求 return () => abortController.abort(); }, []); return <div>{JSON.stringify(data)}</div>; }

2.6 超时处理(手动实现)

async function fetchWithTimeout(url, options = {}, timeout = 5000) { const controller = new AbortController(); const { signal } = controller; const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('请求超时'); } throw error; } } // 使用 fetchWithTimeout('https://api.example.com/data', {}, 3000) .then(res => res.json()) .then(data => console.log(data)) .catch(err => console.error(err.message));

3. axios 详解

3.1 安装与配置

npminstallaxios
import axios from 'axios'; // 全局配置 axios.defaults.baseURL = 'https://api.example.com'; axios.defaults.timeout = 5000; axios.defaults.headers.common['Authorization'] = 'Bearer token'; axios.defaults.headers.post['Content-Type'] = 'application/json';

3.2 基础 GET 请求

import { useState, useEffect } from 'react'; import axios from 'axios'; function AxiosUsers() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { axios.get('https://jsonplaceholder.typicode.com/users') .then(response => { setUsers(response.data); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, []); if (loading) return <div>加载中...</div>; if (error) return <div>错误: {error}</div>; return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }

3.3 POST 请求

async function createPost(postData) { try { const response = await axios.post( 'https://jsonplaceholder.typicode.com/posts', postData, { headers: { 'Content-Type': 'application/json', }, } ); console.log('创建成功:', response.data); return response.data; } catch (error) { console.error('创建失败:', error.response?.data || error.message); throw error; } } // 使用 createPost({ title: 'My Post', body: 'This is the content', userId: 1 });

3.4 并发请求

async function fetchMultipleData() { try { const [usersResponse, postsResponse, commentsResponse] = await Promise.all([ axios.get('https://api.example.com/users'), axios.get('https://api.example.com/posts'), axios.get('https://api.example.com/comments'), ]); return { users: usersResponse.data, posts: postsResponse.data, comments: commentsResponse.data, }; } catch (error) { console.error('并发请求失败:', error); throw error; } }

3.5 请求拦截器

// 请求拦截器 axios.interceptors.request.use( config => { // 在请求发送前做些什么 console.log('请求发送:', config.url); // 添加 token const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); } ); // 响应拦截器 axios.interceptors.response.use( response => { // 对响应数据做点什么 console.log('响应接收:', response.config.url); return response; }, error => { // 对响应错误做点什么 if (error.response?.status === 401) { // 未授权,跳转到登录页 window.location.href = '/login'; } return Promise.reject(error); } );

3.6 创建实例

// 创建多个实例,不同配置 const apiClient = axios.create({ baseURL: 'https://api.example.com', timeout: 5000, headers: { 'Content-Type': 'application/json', }, }); const uploadClient = axios.create({ baseURL: 'https://upload.example.com', timeout: 30000, headers: { 'Content-Type': 'multipart/form-data', }, }); // 使用实例 apiClient.get('/users').then(res => console.log(res.data)); uploadClient.post('/image', formData).then(res => console.log(res.data));

3.7 取消请求

import { CancelToken } from 'axios'; function CancelableAxios() { useEffect(() => { const source = CancelToken.source(); axios.get('https://api.example.com/slow-endpoint', { cancelToken: source.token, }) .then(response => console.log(response.data)) .catch(err => { if (axios.isCancel(err)) { console.log('请求已取消:', err.message); } else { console.error('错误:', err); } }); return () => source.cancel('组件卸载,取消请求'); }, []); }

3.8 上传进度

function UploadFile() { const [progress, setProgress] = useState(0); const handleUpload = async (file) => { const formData = new FormData(); formData.append('file', file); try { const response = await axios.post('/upload', formData, { headers: { 'Content-Type': 'multipart/form-data', }, onUploadProgress: (progressEvent) => { const percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); setProgress(percentCompleted); }, }); console.log('上传成功:', response.data); } catch (error) { console.error('上传失败:', error); } }; return ( <div> <input type="file" onChange={(e) => handleUpload(e.target.files[0])} /> {progress > 0 && <progress value={progress} max="100" />} </div> ); }

4. React 组件中的数据获取模式

4.1 useEffect 模式

function DataFetchingComponent() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let isMounted = true; const fetchData = async () => { try { const response = await axios.get('/api/data'); if (isMounted) { setData(response.data); setLoading(false); } } catch (err) { if (isMounted) { setError(err.message); setLoading(false); } } }; fetchData(); return () => { isMounted = false; }; }, []); if (loading) return <Spinner />; if (error) return <Error message={error} />; return <DataDisplay data={data} />; }

4.2 自定义 Hook 封装

// useFetch Hook function useFetch(url, options = {}) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const abortController = new AbortController(); const fetchData = async () => { try { setLoading(true); const response = await fetch(url, { ...options, signal: abortController.signal, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); setData(result); setError(null); } catch (err) { if (err.name !== 'AbortError') { setError(err.message); } } finally { setLoading(false); } }; fetchData(); return () => abortController.abort(); }, [url]); return { data, loading, error }; } // 使用自定义 Hook function UserProfile({ userId }) { const { data: user, loading, error } = useFetch( `https://api.example.com/users/${userId}` ); if (loading) return <Spinner />; if (error) return <Error message={error} />; return <div>{user?.name}</div>; }

4.3 依赖参数的重新获取

function UserPosts({ userId }) { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { if (!userId) return; let isMounted = true; setLoading(true); axios.get(`/api/users/${userId}/posts`) .then(response => { if (isMounted) { setPosts(response.data); setLoading(false); } }) .catch(error => { if (isMounted) { console.error(error); setLoading(false); } }); return () => { isMounted = false; }; }, [userId]); // userId 变化时重新获取 return ( <div> {loading ? <Spinner /> : <PostList posts={posts} />} </div> ); }

4.4 条件请求

function ConditionalFetch({ shouldFetch, userId }) { const [data, setData] = useState(null); useEffect(() => { if (!shouldFetch || !userId) return; axios.get(`/api/users/${userId}`) .then(response => setData(response.data)); }, [shouldFetch, userId]); return <div>{data?.name}</div>; }

4.5 防抖搜索

import { debounce } from 'lodash'; function SearchComponent() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const searchAPI = useCallback( debounce(async (searchTerm) => { if (!searchTerm) { setResults([]); setLoading(false); return; } setLoading(true); try { const response = await axios.get('/api/search', { params: { q: searchTerm }, }); setResults(response.data); } catch (error) { console.error(error); } finally { setLoading(false); } }, 500), [] ); const handleChange = (e) => { const value = e.target.value; setQuery(value); searchAPI(value); }; return ( <div> <input type="text" value={query} onChange={handleChange} placeholder="搜索..." /> {loading && <div>搜索中...</div>} <ul> {results.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </div> ); }

5. 错误处理最佳实践

5.1 全局错误处理

// axios 全局错误处理 axios.interceptors.response.use( response => response, error => { // 网络错误 if (!error.response) { console.error('网络错误:', error.message); return Promise.reject(new Error('网络连接失败')); } // HTTP 状态码错误 const { status, data } = error.response; switch (status) { case 400: console.error('请求参数错误:', data); break; case 401: console.error('未授权,请重新登录'); // 跳转到登录页 break; case 403: console.error('无权限访问'); break; case 404: console.error('资源不存在'); break; case 500: console.error('服务器错误'); break; default: console.error(`HTTP ${status}:`, data); } return Promise.reject(error); } );

5.2 重试机制

async function fetchWithRetry(url, options = {}, retries = 3, delay = 1000) { for (let i = 0; i < retries; i++) { try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response; } catch (error) { if (i === retries - 1) throw error; await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i))); } } } // 使用 fetchWithRetry('https://api.example.com/unstable') .then(res => res.json()) .then(data => console.log(data)) .catch(err => console.error('重试失败:', err));

6. 性能优化

6.1 请求缓存

const cache = new Map(); async function fetchWithCache(url) { if (cache.has(url)) { console.log('从缓存返回'); return cache.get(url); } const response = await fetch(url); const data = await response.json(); cache.set(url, data); return data; }

6.2 请求去重

const pendingRequests = new Map(); async function fetchWithDedupe(url) { if (pendingRequests.has(url)) { console.log('等待中的请求'); return pendingRequests.get(url); } const promise = fetch(url).then(res => res.json()); pendingRequests.set(url, promise); try { const result = await promise; return result; } finally { pendingRequests.delete(url); } }

7. React 19 中的数据获取

7.1 新的 use 钩子

import { use } from 'react'; // 创建 Promise const fetchUsers = fetch('https://api.example.com/users') .then(res => res.json()); function UserList() { // use 钩子会暂停渲染直到 Promise resolve const users = use(fetchUsers); return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); } // 配合 Suspense 使用 function App() { return ( <Suspense fallback={<div>加载中...</div>}> <UserList /> </Suspense> ); }

7.2 结合 Server Components

// 在 Server Component 中直接使用 async/await async function ServerComponent() { const response = await fetch('https://api.example.com/data'); const data = await response.json(); return ( <div> <h1>{data.title}</h1> <p>{data.description}</p> </div> ); }

8. 完整示例:用户管理面板

import { useState, useEffect, useCallback } from 'react'; import axios from 'axios'; // API 客户端配置 const api = axios.create({ baseURL: 'https://jsonplaceholder.typicode.com', timeout: 5000, }); // 请求拦截器 api.interceptors.request.use(config => { console.log(`[${config.method?.toUpperCase()}] ${config.url}`); return config; }); // 响应拦截器 api.interceptors.response.use( response => response, error => { console.error('API Error:', error.response?.status, error.message); return Promise.reject(error); } ); function UserManagement() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [formData, setFormData] = useState({ name: '', email: '' }); // 获取用户列表 const fetchUsers = useCallback(async () => { try { setLoading(true); const response = await api.get('/users'); setUsers(response.data); setError(null); } catch (err) { setError(err.message); } finally { setLoading(false); } }, []); // 创建用户 const createUser = async (userData) => { try { const response = await api.post('/users', userData); setUsers(prev => [...prev, response.data]); return response.data; } catch (err) { console.error('创建失败:', err); throw err; } }; // 更新用户 const updateUser = async (id, userData) => { try { const response = await api.put(`/users/${id}`, userData); setUsers(prev => prev.map(user => user.id === id ? { ...user, ...response.data } : user )); return response.data; } catch (err) { console.error('更新失败:', err); throw err; } }; // 删除用户 const deleteUser = async (id) => { try { await api.delete(`/users/${id}`); setUsers(prev => prev.filter(user => user.id !== id)); } catch (err) { console.error('删除失败:', err); throw err; } }; useEffect(() => { fetchUsers(); }, [fetchUsers]); const handleSubmit = async (e) => { e.preventDefault(); await createUser(formData); setFormData({ name: '', email: '' }); }; if (loading) return <div>加载用户列表...</div>; if (error) return <div>错误: {error}</div>; return ( <div> <h1>用户管理</h1> {/* 创建表单 */} <form onSubmit={handleSubmit}> <input type="text" placeholder="姓名" value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} /> <input type="email" placeholder="邮箱" value={formData.email} onChange={(e) => setFormData({ ...formData, email: e.target.value })} /> <button type="submit">创建用户</button> </form> {/* 用户列表 */} <ul> {users.map(user => ( <li key={user.id}> {user.name} - {user.email} <button onClick={() => deleteUser(user.id)}>删除</button> </li> ))} </ul> </div> ); } export default UserManagement;

9. 总结

选择建议

场景推荐理由
简单项目、原型fetch原生支持,无需额外依赖
大型项目、复杂需求axios功能丰富,拦截器、取消请求等
需要上传进度axios原生支持 onUploadProgress
需要超时控制axios原生支持 timeout 配置
需要兼容老浏览器axios自动 polyfill
React 19 + Suspensefetch + use原生支持,配合新特性

决策树

需要数据获取? │ ▼ 项目规模? │ ┌───┴───┐ 小 大 │ │ ▼ ▼ fetch axios │ │ └───┬───┘ ▼ 是否需要 高级特性? │ ┌───┴───┐ 是 否 │ │ ▼ ▼ axios fetch

记忆口诀

数据获取两把刀,fetch 原生 axios 强
简单用 fetch 轻巧,复杂用 axios 功能全
拦截取消都支持,上传进度更流畅


10. 相关资源

  • MDN fetch 文档
  • axios 官方文档
  • React 数据获取最佳实践
  • React 19 use 钩子

11. 练习

基础练习

  1. 使用 fetch 创建一个天气查询组件
  2. 使用 axios 实现一个 GitHub 用户搜索功能

进阶练习

  1. 实现一个带有请求缓存和去重的数据获取 Hook
  2. 创建一个支持自动重试和错误边界的数据获取组件

http://www.jsqmd.com/news/1074863/

相关文章:

  • 绕过Cloudflare挑战实现ChatGPT自动化访问的技术方案与实践
  • 2023年AI工程化实战手册:从RAG、微调到CUDA排错
  • 告别LLM能力边界!30分钟掌握AI Tools调用核心逻辑
  • AI作为神经多样性协作者:本地化轻量工具赋能阿斯伯格日常
  • 印刷服务实测:零起印量、24小时交付与1.2%色差的技术拆解
  • 从链表到哈希表:数组索引与链表指针如何协作
  • GitHub爆火Skill三巨头实测:选错直接让AI代码精神分裂
  • GPT Image 1.5 国内直连接入实战:低成本高稳定图像生成方案
  • 柏浪涛刑法网课资源|柏浪涛刑法百度|柏浪涛刑法百度云
  • 04-性能优化与最佳实践——06. React Compiler - 自动记忆化
  • 【WorkBuddy专栏42】初学编程用AI助手是捷径还是陷阱——正确使用方法的深度解析
  • ebgp邻居非直连无法建立邻居解决方法
  • AI仿生手实战指南:轻量TCN模型驱动的低延迟肌电控制
  • HEVC(十八):运动估计
  • 2025年AI落地实战:小模型、边缘部署与人机共生
  • HoRain云--Codex提示词优化五大实战技巧
  • Burp Suite抓包全攻略:从浏览器到手机端的HTTPS流量捕获与安全测试
  • 从第一根白发到满头花白,变白进程真的能干预吗?
  • 直播弹幕实时情感分析系统:从数据采集到异常预警的完整设计
  • GitLab高危漏洞CVE-2025-5121应急响应实战:从分析到升级加固全记录
  • 基于Wireshark与Suricata的加密WebShell流量检测实战
  • 如何5分钟完成Word到LaTeX的完美转换:docx2tex完整指南
  • 有限元静力学计算验证-有理论计算结果对比——网格对弧形结构影响较大,矩形影响不大。——采用了一维线体梁单元-横截面矩形和圆形对比-三维计算结果对比-矩形表面和圆柱形表面!
  • 快来薅羊毛!千问App新用户快速白嫖8元无门槛通用券,下载千问,输入口令:千问新用户专属876028,就可以领取啦
  • 新型公共办公插件与测绘单机软件精选推荐
  • 2026降AIGC软件亲测:10款工具对比,论文过审技巧盘点
  • ebgp邻居非直连无法建立邻居解决方法(2)
  • 科研实验领域高速摄像机的使用体验
  • 微信小程序抓包实战教程:Proxifier+Fiddler+Burp Suite三件套配置与HTTPS解密全流程
  • 论文写不出学术味?高校导师推荐这几个AI论文软件