React组件重渲染时如何避免fetch重复触发数据请求
fetch重复触发的核心原因是你的数据请求逻辑没有和高频更新的倒计时状态做隔离:倒计时每秒更新num状态触发组件重渲染,若fetch逻辑写在组件函数体内部、或是依赖数组包含num的useEffect中,就会跟着重渲染/effect重跑反复执行,最终每秒拉取一次新数据。
同时你当前的倒计时写法存在隐患:每次重渲染都会重新声明timer变量,没有做定时器清理,闭包引用的num值也容易出现闭包陈旧值问题。
1. 隔离fetch请求,保证只执行一次
因为你是通过useContext获取题目数组,直接把fetch逻辑挪到Context Provider的层级,写入依赖数组为空的useEffect中,这段逻辑只会在Provider首次挂载时执行一次,拉取到数据后存入Context的state即可,后续所有消费Context的组件都直接读取缓存好的数组,不会重复触发请求:
// QuizContext Provider层代码示例 const QuizProvider = ({ children }) => { const [quizList, setQuizList] = useState([]); // 空依赖useEffect,仅挂载时执行一次 useEffect(() => { const fetchQuizData = async () => { const res = await fetch('你的接口地址'); const data = await res.json(); setQuizList(data); }; fetchQuizData(); }, []); // 空数组是关键,无任何依赖就不会重跑 return ( <QuizContext.Provider value={{ quizList }}> {children} </QuizContext.Provider> ) }
不要在Quiz组件内部写fetch逻辑,Quiz组件只需要通过useContext读取已经缓存好的quizList即可,不管组件怎么重渲染,只要不手动调用setQuizList,数组内容就不会变动。
2. 修正倒计时逻辑,避免不必要的重复执行
把你的倒计时逻辑改成函数式状态更新,加上定时器清理,不要在倒计时相关的useEffect里混入任何数据请求代码:
const Quiz = () =>{ const [num, setNum] = useState(10); // 初始倒计时时长自行设置 const { quizList } = useContext(QuizContext); // 直接读context里缓存好的题目数据 useEffect(() => { const timer = setTimeout(() => { // 函数式更新拿最新的状态值,避免闭包陈旧值问题 setNum(prevNum => { if (prevNum <= 1) { nextQuestion(); return 0; } return prevNum - 1; }); }, 1000); // 清理函数:组件卸载/下一次定时器设置前清除上一个定时器,防止内存泄漏 return () => clearTimeout(timer); }, [num]); return(...) }
React组件每次状态更新触发重渲染时,函数体内的普通代码都会从头执行一遍。只有放在依赖数组为空的
useEffect中的逻辑,会在组件首次挂载时执行一次,后续重渲染不会重复触发。
永远不要把数据请求逻辑和高频更新的状态(倒计时、滚动位置、鼠标坐标等)放在同一个useEffect中,也不要把高频更新的状态加入数据请求effect的依赖数组,否则必然会导致重复请求。
内容的提问来源于stack exchange,提问作者Luis Gonzalez

