React中如何在路由切换时预取数据却导致重复请求?

UI洛熙 阅读 56

我在React应用里用useParams获取文章ID后,用useEffect在组件挂载时预取文章详情数据。但当我切换到其他页面再返回时,发现fetchData又触发了重复请求,这样会不会浪费带宽?


function ArticlePage() {
  const { id } = useParams();
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const res = await api.getArticle(id);
      setData(res.data);
    };
    fetchData();
  }, [id]); // 我尝试把id放进依赖数组,但来回切换页面还是重复请求

  return <div>{data?.content || '加载中'}</div>;
}

我试过在useEffect返回清理函数,但不确定该怎么正确拦截之前的请求。如果不用useParams改用路由守卫预取会不会更好?

我来解答 赞 6 收藏
二维码
手机扫码查看
2 条解答
程序员英歌
你这个问题确实挺常见的,React里这种重复请求的情况很容易遇到。简单说下原因:每次路由切换都会重新触发组件的useEffect,即使id没变,组件本身可能已经卸载再挂载了,所以fetch又跑了一遍。

要解决这个,可以试试下面几种优雅的方式:

### 方法1:用memo缓存数据
这样更清晰,把数据缓存起来,避免重复请求。
import { useMemo, useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';

function ArticlePage() {
const { id } = useParams();
const [data, setData] = useState(null);

const cachedData = useMemo(() => {
return data; // 把数据缓存起来
}, [data]);

useEffect(() => {
if (cachedData) return; // 如果已经有数据,就不重新请求

const fetchData = async () => {
const res = await api.getArticle(id);
setData(res.data);
};
fetchData();
}, [id, cachedData]); // 注意这里要把cachedData加到依赖数组

return <div>{data?.content || '加载中'}</div>;
}


### 方法2:封装一个自定义hook
直接写个useFetchWithCache,逻辑抽出去,代码会清爽很多。
function useFetchWithCache(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchData = async () => {
if (data) return; // 已经有数据就跳过
const res = await fetch(url);
const json = await res.json();
setData(json);
setLoading(false);
};
fetchData();
}, [url, data]);

return { data, loading };
}

function ArticlePage() {
const { id } = useParams();
const { data, loading } = useFetchWithCache(/api/article/${id});

if (loading) return <div>加载中</div>;
return <div>{data?.content}</div>;
}


### 方法3:用Redux或Context管理全局状态
如果项目大一点,建议直接上状态管理工具(比如Redux、 Zustand等),把文章数据放到全局state里。这样不管页面怎么切,数据都存在,不需要重复请求。

至于你说的路由守卫预取,也不是不行,但我觉得有点重手了。守卫更适合做一些权限校验或者全局拦截,单纯为了预取数据有点杀鸡用牛刀的感觉。

以上方法随便选一个实现就行,我个人推荐方法2,封装成hook之后代码看起来舒服多了,而且复用性也高。
点赞 10
2026-02-02 10:07
皇甫胜换
直接用 React Query 吧,这种场景它天生就是干这个的。不过如果你不想引入新库,可以试试下面的方法:

你重复请求的问题是因为每次组件重新挂载时,useEffect 会重新执行一次 fetch 操作。虽然这是预期行为,但确实有点浪费带宽。解决办法是加个简单的缓存机制。

import { useState, useEffect, useMemo } from 'react';
import { useParams } from 'react-router-dom';

// 创建一个全局缓存对象
let cache = {};

function ArticlePage() {
const { id } = useParams();
const [data, setData] = useState(cache[id]);

useEffect(() => {
if (cache[id]) {
console.log('从缓存中读取数据');
setData(cache[id]);
} else {
console.log('从接口获取数据');
const fetchData = async () => {
try {
const res = await api.getArticle(id);
cache[id] = res.data; // 缓存数据
setData(res.data);
} catch (error) {
console.error('请求失败:', error);
}
};
fetchData();
}

// 清理函数可选,如果需要的话
return () => {
console.log('清理逻辑');
};
}, [id]);

return (

{data ? (
{data.content}

) : (
加载中...
)}

);
}

export default ArticlePage;


这样写后,第一次请求的数据会被缓存起来,下次再访问相同 ID 的文章时,就直接从内存里拿数据了。不过记得如果数据会变化,要定期清空缓存或者加个过期时间。

说实话,这种手写缓存的方式在复杂项目里还是挺蛋疼的,建议直接用 React Query 这种专业的状态管理工具,省心多了。
点赞 14
2026-01-28 23:08