React中如何用缓存策略避免重复的API请求?

欧阳柯汝 阅读 29

我在开发一个新闻列表页面时遇到问题,每次切换标签页再回来,组件都会重新发起API请求。虽然用了useMemo缓存了数据,但发现浏览器开发者工具里还是显示重复请求:


useEffect(() => {
  const fetchNews = async () => {
    const res = await fetch('/api/news');
    setNews(await res.json());
  };
  fetchNews();
}, [location.pathname]); // 依赖路径变化重新获取

尝试过在fetch头里加Cache-Control:max-age=300,但切换标签页回来还是有新请求。如果设置长时间缓存会不会导致数据不及时更新?有没有更好的React组件级缓存方案?

我来解答 赞 7 收藏
二维码
手机扫码查看
2 条解答
东方心虹
你这个情况很典型,useMemo缓存的是值,但fetch请求本身在useEffect里还是会执行,所以useMemo根本拦不住请求发出。而且浏览器的Cache-Control对跨域请求或者带凭证的请求不一定生效,特别是你用了fetch默认不走强缓存。

先调试看看:打开Network面板,点一个请求,看Size列是不是显示from disk cache或from memory cache。如果没看到,说明根本没有命中缓存。

解决方案分两步:

第一,用组件状态 + 时间戳做内存缓存,避免重复请求。简单搞个全局对象或者Map存请求结果和时间,比如:

const cache = new Map();

function getNewsFromCache(url, maxAge = 300000) { // 5分钟
const cached = cache.get(url);
if (!cached) return null;
const isExpired = Date.now() - cached.timestamp > maxAge;
if (isExpired) {
cache.delete(url);
return null;
}
return cached.data;
}


然后在组件里:

useEffect(() => {
const url = '/api/news';
const cachedData = getNewsFromCache(url);
if (cachedData) {
setNews(cachedData);
return;
}

const fetchNews = async () => {
const res = await fetch(url);
const data = await res.json();
cache.set(url, {
data,
timestamp: Date.now()
});
setNews(data);
};

fetchNews();
}, [location.pathname]);


第二,如果你希望用户切回来时数据不要太旧,可以结合visibilityChange做刷新判断:

useEffect(() => {
const handleVisibilityChange = () => {
if (!document.hidden && location.pathname === '/news') {
// 页面激活时检查缓存是否过期
const cached = cache.get('/api/news');
if (!cached || Date.now() - cached.timestamp > 60000) { // 超过1分钟就刷新
fetchNews(); // 重新请求
}
}
};

document.addEventListener('visibilitychange', handleVisibilityChange);
return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
}, [location.pathname]);


这样既避免频繁请求,又能保证切回来时数据不至于太老。max-age你自己按业务调,新闻类30秒到2分钟都行。

要是你还想更进一步,可以用useSWR或者React Query这种库,自带stale-while-revalidate策略,比手写清爽多了。不过现在这么改完应该就能解决问题了。
点赞 2
2026-02-12 18:22
码农艳清
你这问题很常见,useMemo只能缓存数据,不能阻止副作用触发。你用location.pathname作为依赖触发API请求,每次切换标签页回来都会重新执行useEffect,即使数据是缓存的,请求还是发了。

**性能上**要考虑两个层面:

1. **客户端缓存策略**:浏览器本身有HTTP缓存机制,但默认行为可能不够用。你加的Cache-Control:max-age=300是正确的方向,但浏览器是否真正复用缓存取决于请求方式是否一致,比如请求头、URL是否完全一样。你可以用fetchcache选项更明确控制:

fetch('/api/news', {
cache: 'force-cache', // 或 'default'
})


这样浏览器会优先读取缓存。

2. **组件级状态缓存**:useMemo能缓存值,但不能阻止useEffect触发。你需要把**请求逻辑和组件生命周期解耦**,可以加一层状态控制:

const [news, setNews] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
let isSubscribed = true;

const fetchNews = async () => {
const res = await fetch('/api/news', { cache: 'force-cache' });
if (isSubscribed) {
setNews(await res.json());
setLoading(false);
}
};

if (!news) {
fetchNews();
} else {
setLoading(false);
}

return () => {
isSubscribed = false;
};
}, [news]);


这样组件重新渲染时如果news已经存在,就不会重新请求。

**另外注意**:如果你担心缓存时间太长导致数据不更新,可以用一个中间层缓存策略,比如设置较短的max-age加上stale-while-revalidate

Cache-Control: max-age=60, stale-while-revalidate=300


这样浏览器可以先用旧数据渲染,后台静默更新,兼顾性能和新鲜度。

综上,HTTP缓存 + 组件状态控制,才是稳定方案。
点赞 3
2026-02-06 17:43