React应用中正确调用API及规避429请求过多错误方案
问题解答
1. 当前请求写法是否合理?
当前写法不合理。核心问题在于:
- 组件每次挂载(比如路由切换回该页面、父组件重渲染导致ListTour重新挂载)都会触发空依赖的
useEffect,重复发起API请求; - 完全没有缓存机制,多次访问同一页面会频繁触发请求,很容易触发服务端的请求频率限制,也就是你遇到的429错误。
2. 实现首次请求+缓存的几种可行方案
方案一:组件内本地缓存(适合单组件复用场景)
用useRef存储缓存数据(不会随组件渲染重置),组件挂载时先检查缓存,有数据直接复用,没有再发起请求。缺点是组件销毁后缓存会丢失,比如路由切换离开再回来会重新请求。
修改后的组件代码:
import React, { useEffect, useState, useRef } from 'react'; import { Link } from "react-router-dom"; import classes from "./listTour.module.css"; import { URL } from '../../../URL' import axios from "axios"; const ListTour = (props) => { const [cardTour, setCardTour] = useState([]); // 用useRef存缓存,避免组件渲染时重置 const tourCache = useRef(null); useEffect(() => { const getListTournaments = async () => { // 先读缓存,有数据直接用 if (tourCache.current) { setCardTour(tourCache.current); return; } try { const tours = await axios.get(`${URL}/tournaments`) console.log(tours.data) // 写入缓存 tourCache.current = tours.data; setCardTour(tours.data) } catch (error) { console.log(error) } } getListTournaments() }, []) return ( cardTour.map(tour => { return ( <Link key={tour.id} to={`Profile/${tour.name_tour_sort}`}> <div className={classes.cardTour}> <div className={classes.cardTourAdaptive}> <div className={classes.cardTourBanner}> <img className={classes.cardTourBannerImg} src={`/images/LogoAndBannerTour/${tour.banner_tour}`} alt={`${tour.name_tour}`}/> </div> <div className={classes.nameTour}>{tour.name_tour}</div> <div className={classes.prizePoolTour}>{tour.prize_pool}</div> <span className={classes.DateTour}>{tour.date_tour}</span> </div> </div> </Link> ) }) ); }; export default ListTour;
方案二:全局缓存(适合跨组件/路由共享数据)
如果需要在整个应用的不同页面复用赛事数据,路由切换后也保留缓存,可以单独写一个缓存管理模块:
- 创建
tournamentCache.js缓存模块:
// 全局存储缓存数据 let tournamentData = null; export const getTournaments = async (axiosInstance, baseUrl) => { // 有缓存直接返回 if (tournamentData) { return tournamentData; } // 无缓存则发起请求并写入缓存 const res = await axiosInstance.get(`${baseUrl}/tournaments`); tournamentData = res.data; return tournamentData; }; // 可选:提供清除缓存的方法,用于数据更新时手动刷新 export const clearTournamentCache = () => { tournamentData = null; };
- 在组件中使用该模块:
import React, { useEffect, useState } from 'react'; import { Link } from "react-router-dom"; import classes from "./listTour.module.css"; import { URL } from '../../../URL' import axios from "axios"; import { getTournaments } from '../path/to/tournamentCache'; const ListTour = (props) => { const [cardTour, setCardTour] = useState([]); useEffect(() => { const fetchData = async () => { try { const tours = await getTournaments(axios, URL); setCardTour(tours); } catch (error) { console.log(error) } } fetchData() }, []) // 渲染部分和原代码一致... }; export default ListTour;
这种方式的缓存全局有效,只要应用不刷新,任何组件调用getTournaments都会复用已有数据。
方案三:Axios拦截器统一处理缓存
通过Axios的请求/响应拦截器,对指定接口自动做缓存,不需要在组件中单独处理:
- 初始化带拦截器的Axios实例:
import axios from 'axios'; import { URL } from './URL'; const axiosInstance = axios.create({ baseURL: URL }); // 缓存存储容器 const requestCache = new Map(); // 请求拦截器:检查缓存,有则直接返回缓存数据 axiosInstance.interceptors.request.use(config => { // 只对GET请求的tournaments接口做缓存 if (config.method === 'get' && config.url === '/tournaments') { const cachedData = requestCache.get('/tournaments'); if (cachedData) { // 直接返回缓存的响应,跳过实际请求 return Promise.resolve({ data: cachedData }); } } return config; }); // 响应拦截器:成功响应后写入缓存 axiosInstance.interceptors.response.use(response => { if (response.config.method === 'get' && response.config.url === '/tournaments') { requestCache.set('/tournaments', response.data); } return response; }); export default axiosInstance;
- 组件中使用该Axios实例:
// 导入自定义的Axios实例 import axiosInstance from '../path/to/axiosInstance'; // 修改useEffect中的请求代码 const tours = await axiosInstance.get('/tournaments')
这种方式可以统一管理多个接口的缓存,适合多组件调用同一接口的场景。
补充提示
如果赛事数据会定期更新,可以给缓存添加过期时间,或者在数据更新后调用清除缓存的方法,确保用户能看到最新数据。另外,429错误除了缓存,也可以搭配请求重试机制,但缓存是从根源减少请求的最优解。
内容的提问来源于stack exchange,提问作者wyth1k
相关产品推荐
相关产品推荐

