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

React数据请求

下面,我们来系统的梳理关于 React 数据请求:fetch / axios + useEffect 的基本知识点:


一、React 数据交互概述

1.1 为什么需要数据请求?

现代 React 应用通常需要与后端 API 交互以获取或提交数据。数据请求是连接前端 UI 和后端服务的桥梁,实现动态内容展示、用户交互和数据持久化。

1.2 核心概念

  • HTTP 请求:GET(获取数据)、POST(创建数据)、PUT(更新数据)、DELETE(删除数据)
  • 异步操作:JavaScript 的非阻塞特性,不会阻塞 UI 渲染
  • 副作用管理:数据请求属于副作用,需要在组件生命周期中合理管理

二、fetch API 详解

2.1 基础使用

// GET 请求
fetch('https://api.example.com/data').then(response => {if (!response.ok) {throw new Error('网络响应异常');}return response.json();}).then(data => console.log(data)).catch(error => console.error('请求失败:', error));// POST 请求
fetch('https://api.example.com/data', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({title: '新标题',content: '这是内容'})
})
.then(response => response.json())
.then(data => console.log(data));

2.2 fetch 特点

  • 内置现代浏览器:无需额外安装
  • Promise-based:支持链式调用
  • 低层级 API:需要手动处理更多细节
  • 默认不带 cookie:需要设置 credentials: 'include'
  • 错误处理:不会 reject HTTP 错误状态(如 404、500)

三、axios 库详解

3.1 基础使用

npm install axios
import axios from 'axios';// GET 请求
axios.get('https://api.example.com/data').then(response => console.log(response.data)).catch(error => console.error('请求失败:', error));// POST 请求
axios.post('https://api.example.com/data', {title: '新标题',content: '这是内容'
}, {headers: {'Content-Type': 'application/json'}
})
.then(response => console.log(response.data));

3.2 axios 特点

  • 功能更全面:自动转换 JSON 数据
  • 错误处理更智能:HTTP 错误状态会触发 catch
  • 拦截器支持:请求/响应拦截
  • 取消请求:内置取消令牌
  • 并发请求axios.all()axios.spread()
  • 浏览器兼容性:支持旧版浏览器

3.3 fetch vs axios 对比

特性fetchaxios
安装内置需安装
语法简洁度⭐⭐⭐⭐⭐⭐
JSON 处理需手动转换自动转换
错误处理需手动处理 HTTP 错误自动处理 HTTP 错误
超时设置不支持原生支持
取消请求AbortControllerCancelToken
拦截器不支持支持
下载进度支持支持
上传进度不支持支持
浏览器兼容现代浏览器更广泛

四、useEffect 与数据请求

4.1 useEffect 基础

import { useEffect } from 'react';function DataComponent() {useEffect(() => {// 数据请求逻辑const fetchData = async () => {try {const response = await fetch('https://api.example.com/data');const data = await response.json();console.log(data);} catch (error) {console.error('请求失败:', error);}};fetchData();}, []); // 空依赖数组表示仅在组件挂载时执行return <div>数据组件</div>;
}

4.2 依赖数组详解

  • 空数组 []:仅在组件挂载时执行一次
  • 特定依赖 [dep1, dep2]:依赖变化时重新执行
  • 无依赖数组:每次渲染后都执行
// 根据 ID 获取数据
function UserProfile({ userId }) {const [user, setUser] = useState(null);useEffect(() => {const fetchUser = async () => {const response = await axios.get(`/api/users/${userId}`);setUser(response.data);};fetchUser();}, [userId]); // userId 变化时重新获取数据// 渲染用户信息...
}

五、完整数据请求模式

5.1 基础实现(状态管理)

import { useState, useEffect } from 'react';
import axios from 'axios';function DataFetcher() {const [data, setData] = useState([]);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {const fetchData = async () => {try {setLoading(true);const response = await axios.get('https://api.example.com/data');setData(response.data);setError(null);} catch (err) {setError(err.message);} finally {setLoading(false);}};fetchData();}, []);if (loading) return <div>加载中...</div>;if (error) return <div>错误: {error}</div>;return (<ul>{data.map(item => (<li key={item.id}>{item.name}</li>))}</ul>);
}

5.2 进阶实现(取消请求)

import { useState, useEffect } from 'react';
import axios from 'axios';function SafeDataFetcher() {const [data, setData] = useState([]);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {// 创建取消令牌const cancelTokenSource = axios.CancelToken.source();const fetchData = async () => {try {setLoading(true);const response = await axios.get('https://api.example.com/data', {cancelToken: cancelTokenSource.token});setData(response.data);setError(null);} catch (err) {if (!axios.isCancel(err)) {setError(err.message);}} finally {if (!cancelTokenSource.token.reason) {setLoading(false);}}};fetchData();// 清理函数:取消未完成的请求return () => {cancelTokenSource.cancel('组件卸载,取消请求');};}, []);// 渲染逻辑...
}

5.3 使用 AbortController(fetch 取消)

useEffect(() => {const abortController = new AbortController();const fetchData = async () => {try {setLoading(true);const response = await fetch('https://api.example.com/data', {signal: abortController.signal});if (!response.ok) throw new Error('请求失败');const data = await response.json();setData(data);} catch (err) {if (err.name !== 'AbortError') {setError(err.message);}} finally {setLoading(false);}};fetchData();return () => {abortController.abort();};
}, []);

六、高级数据请求模式

6.1 并行请求

useEffect(() => {const fetchAllData = async () => {try {setLoading(true);// 使用 Promise.all 并行请求const [usersResponse, postsResponse] = await Promise.all([axios.get('/api/users'),axios.get('/api/posts')]);setUsers(usersResponse.data);setPosts(postsResponse.data);} catch (err) {setError(err.message);} finally {setLoading(false);}};fetchAllData();
}, []);

6.2 顺序请求

useEffect(() => {const fetchSequentialData = async () => {try {setLoading(true);// 先获取用户const userResponse = await axios.get(`/api/users/${userId}`);setUser(userResponse.data);// 再获取用户的帖子const postsResponse = await axios.get(`/api/users/${userId}/posts`);setPosts(postsResponse.data);} catch (err) {setError(err.message);} finally {setLoading(false);}};fetchSequentialData();
}, [userId]);

6.3 轮询数据

useEffect(() => {let isMounted = true;const intervalId = setInterval(() => {if (isMounted) {fetchData();}}, 5000); // 每5秒轮询一次// 初始加载fetchData();return () => {isMounted = false;clearInterval(intervalId);};async function fetchData() {try {const response = await axios.get('/api/live-data');setData(response.data);} catch (err) {console.error('轮询失败:', err);}}
}, []);

七、自定义 Hook 封装

7.1 基础数据请求 Hook

import { useState, useEffect } from 'react';
import axios from 'axios';function useFetch(url, options = {}) {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);useEffect(() => {const cancelTokenSource = axios.CancelToken.source();const fetchData = async () => {try {setLoading(true);const response = await axios({url,...options,cancelToken: cancelTokenSource.token});setData(response.data);setError(null);} catch (err) {if (!axios.isCancel(err)) {setError(err.message || '请求失败');}} finally {setLoading(false);}};fetchData();return () => {cancelTokenSource.cancel('请求取消');};}, [url, JSON.stringify(options)]); // 依赖项为 URL 和序列化的 optionsreturn { data, loading, error, refetch: fetchData };
}// 使用示例
function UserList() {const { data: users, loading, error } = useFetch('/api/users');if (loading) return <p>加载中...</p>;if (error) return <p>错误: {error}</p>;return (<ul>{users.map(user => (<li key={user.id}>{user.name}</li>))}</ul>);
}

7.2 高级 Hook(带缓存和刷新)

import { useState, useEffect, useCallback, useRef } from 'react';function useAdvancedFetch(url) {const [data, setData] = useState(null);const [loading, setLoading] = useState(false);const [error, setError] = useState(null);const cache = useRef({});const fetchData = useCallback(async (abortController) => {if (cache.current[url]) {setData(cache.current[url]);return;}setLoading(true);setError(null);try {const signal = abortController ? abortController.signal : null;const response = await fetch(url, { signal });if (!response.ok) throw new Error('请求失败');const result = await response.json();cache.current[url] = result;setData(result);} catch (err) {if (err.name !== 'AbortError') {setError(err.message);}} finally {setLoading(false);}}, [url]);const refetch = useCallback(() => {// 清除缓存并重新获取delete cache.current[url];fetchData();}, [url, fetchData]);useEffect(() => {const abortController = new AbortController();fetchData(abortController);return () => {abortController.abort();};}, [fetchData]);return { data, loading, error, refetch };
}

八、实践与优化

8.1 性能优化策略

  1. 避免不必要请求

    useEffect(() => {// 只有当 userId 存在时才请求if (userId) {fetchUserData(userId);}
    }, [userId]);
    
  2. 防抖搜索请求

    useEffect(() => {const handler = setTimeout(() => {if (query.length > 2) {fetchResults(query);}}, 300); // 300ms 防抖return () => clearTimeout(handler);
    }, [query]);
    
  3. 数据缓存

    const cache = useRef({});useEffect(() => {if (cache.current[userId]) {setUserData(cache.current[userId]);return;}// 否则发起请求...
    }, [userId]);
    

8.2 错误处理最佳实践

  1. 统一错误处理

    try {// 请求逻辑
    } catch (error) {if (axios.isCancel(error)) {console.log('请求已取消', error.message);} else if (error.response) {// 服务器返回错误状态 (4xx, 5xx)console.error('服务器错误:', error.response.status);} else if (error.request) {// 请求已发送但无响应console.error('无响应:', error.request);} else {// 其他错误console.error('请求设置错误:', error.message);}
    }
    
  2. 用户友好错误信息

    const getErrorMessage = (error) => {if (error.response) {switch (error.response.status) {case 401: return '未授权,请登录';case 403: return '拒绝访问';case 404: return '资源不存在';case 500: return '服务器错误';default: return `请求失败: ${error.response.status}`;}}return '网络错误,请重试';
    };
    

8.3 安全实践

  1. 防止 XSS 攻击

    // 避免直接渲染未处理的数据
    <div>{user.bio}</div> // 危险!// 使用 DOMPurify 或处理危险 HTML
    import DOMPurify from 'dompurify';<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(user.bio) }} />
    
  2. 保护敏感信息

    • 永远不要在前端存储 API 密钥
    • 使用环境变量存储敏感配置
    // .env 文件
    REACT_APP_API_URL=https://api.example.com// 组件中使用
    const apiUrl = process.env.REACT_APP_API_URL;
    

九、测试数据请求

9.1 使用 Jest 和 MSW 测试

npm install msw jest-fetch-mock --save-dev
// src/mocks/server.js
import { setupServer } from 'msw/node';
import { rest } from 'msw';const server = setupServer(rest.get('/api/users', (req, res, ctx) => {return res(ctx.json([{ id: 1, name: '用户1' },{ id: 2, name: '用户2' }]));})
);export default server;// 测试文件
import { render, screen, waitFor } from '@testing-library/react';
import server from './mocks/server';
import UserList from '../UserList';// 启动 API 模拟服务器
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());test('加载并显示用户列表', async () => {render(<UserList />);// 初始加载状态expect(screen.getByText(/加载中/i)).toBeInTheDocument();// 等待数据加载完成await waitFor(() => {expect(screen.getByText('用户1')).toBeInTheDocument();expect(screen.getByText('用户2')).toBeInTheDocument();});
});test('处理请求错误', async () => {// 覆盖默认处理程序以模拟错误server.use(rest.get('/api/users', (req, res, ctx) => {return res(ctx.status(500));}));render(<UserList />);await waitFor(() => {expect(screen.getByText(/请求失败/i)).toBeInTheDocument();});
});

十、常见问题与解决方案

10.1 无限请求循环

问题:依赖数组配置不当导致无限请求

// 错误示例:缺少依赖
useEffect(() => {fetchData(id); // id 变化但未在依赖数组中
}, []);// 错误示例:在 effect 中更新依赖项
const [data, setData] = useState([]);
useEffect(() => {fetchData().then(newData => setData(newData));
}, [data]); // data 变化会触发 effect

解决方案

  1. 确保所有依赖项都正确声明
  2. 使用函数式更新避免不必要的依赖
useEffect(() => {fetchData().then(setData); // 正确:setData 是稳定的
}, [fetchData]); // 需要 useCallback 包装 fetchData

10.2 竞态条件

问题:请求返回顺序不确定导致数据错乱

useEffect(() => {let ignore = false;const fetchData = async () => {const result = await axios.get(`/api/data?id=${id}`);if (!ignore) {setData(result.data);}};fetchData();return () => {ignore = true;};
}, [id]);

10.3 内存泄漏

问题:组件卸载后更新状态

useEffect(() => {let isMounted = true;fetchData().then(result => {if (isMounted) {setData(result);}});return () => {isMounted = false;};
}, []);

十一、总结

简单请求
复杂应用
企业级应用
选择数据请求方式
需求复杂度
fetch + useEffect
axios + useEffect
React Query/SWR
基础实现
高级功能实现
数据缓存/自动刷新
处理状态/错误
性能优化
测试覆盖

关键点

  1. 使用 useEffect 管理副作用:确保数据请求与组件生命周期同步
  2. 正确处理加载和错误状态:提升用户体验
  3. 实现请求取消:避免组件卸载后更新状态
  4. 封装自定义 Hook:复用数据请求逻辑
  5. 编写测试:确保数据交互可靠性

选择

  • 小型应用/简单请求:fetch + useEffect
  • 中型应用/需要高级功能:axios + useEffect
  • 大型应用/复杂状态管理:React Query 或 SWR
http://www.lryc.cn/news/620798.html

相关文章:

  • Android 项目:画图白板APP开发(二)——历史点、数学方式推导点
  • 2.0t的涡轮增压器结构设计说明书cad【5张】设计说明说
  • OpenSatKit技术详解
  • 《Leetcode》-面试题-hot100-动态规划
  • C++实现序列匹配与分类处理
  • 深度学习-卷积神经网络CNN-批量归一化 BatchNorm
  • React和Vue
  • React 中播放HLS 视频流 ,超简单的组件高度复用
  • 2019 GPT2原文 Language Models are Unsupervised Multitask Learners - Reading Notes
  • 微美全息(WIMI.US)借区块链与聚类技术,开启物联网去中心化安全架构新纪元
  • C#WPF实战出真汁03--登录功能实现
  • 阿里云Spring Cloud架构分析
  • 无人机双目视觉设计要点概述!
  • .Net4.0 WPF中实现下拉框搜索效果
  • 4. 索引数据的增删改查
  • MyBatis Interceptor 深度解析与应用实践
  • Mybatis学习笔记(一)
  • 【密码学实战】基于SCTP的DTLS协议实验
  • springboot项目不同平台项目通过http接口AES加密传输
  • AR技术赋能电力巡检:智能化升级的“秘密武器”
  • MicroVM-as-a-Service 后端服务架构设计与实现
  • 顺序表插入删除
  • 常见的Jmeter压测问题
  • OpenCV 视频处理全解析
  • 力扣-295.数据流的中位数
  • 11、C 语言字符串函数总结
  • OpenCV 高斯模糊降噪
  • npm删除包
  • PyCharm性能优化与大型项目管理指南
  • C++:浅尝gdb