React中如何配置多个useEffect Hook以避免重复请求并在查询变更时重置页码
解决React中多useEffect触发重复请求及查询重置页码问题
我来帮你搞定这个问题!你现在遇到的核心痛点有两个:一是父组件传来的query变化时,需要自动把页码重置回1;二是避免因query和page先后变动导致的重复请求。咱们一步步来优化你的代码:
第一步:拆分useEffect,单独处理查询条件变化时的页码重置
首先,我们单独写一个useEffect来监听query的变化——只要query更新,就把page重置为初始值1。这样就能保证每次新查询都是从第一页开始,不会停留在之前的页码:
useEffect(() => { // 只有当前页码不是1的时候才重置,避免无意义的状态更新 if (page !== 1) { setPage(1); } }, [query]);
第二步:优化请求逻辑,用请求取消避免重复请求和竞态问题
之前的问题在于,当query变化时,会先触发一次请求,然后page重置又触发一次请求,导致重复调用。另外,还存在竞态风险:如果前一个请求还没返回,新的请求结果先回来了,旧请求的结果可能会覆盖新的,导致页面显示错误数据。
我们可以用Axios的取消令牌来解决这个问题,同时调整请求触发的useEffect:
// 先导入useRef import React, { useState, useEffect, useRef } from "react" // 在组件内部添加一个ref来存储取消令牌 const cancelTokenRef = useRef(null); // 处理请求的useEffect useEffect(() => { // 如果有未完成的请求,先取消它,避免重复请求和竞态 if (cancelTokenRef.current) { cancelTokenRef.current.cancel('请求已取消:参数发生变化'); } // 创建新的取消令牌 cancelTokenRef.current = axios.CancelToken.source(); // 把results改成异步函数,更符合现代写法 const fetchResults = async () => { setIsLoaded(false); setError(null); try { const response = await axios.get(dataUrl, { params: { query: query, page: page }, // 把取消令牌传给当前请求 cancelToken: cancelTokenRef.current.token }); setItems(response.data); } catch (err) { // 忽略取消请求的错误,只处理真正的请求失败 if (!axios.isCancel(err)) { setError(err.message); } } finally { setIsLoaded(true); } }; fetchResults(); // 组件卸载或者依赖变化时,清理未完成的请求 return () => { if (cancelTokenRef.current) { cancelTokenRef.current.cancel('请求已取消:组件卸载或参数变化'); } }; }, [query, page]);
最终的完整代码调整
把这些整合到你的组件里,最终代码大概是这样的:
import React, { useState, useEffect, useRef } from "react" import axios from 'axios' import { Link, useLocation } from "react-router-dom"; export default function DocumentList({query}) { const [page, setPage] = useState(1); const [error, setError] = useState(null); const [isLoaded, setIsLoaded] = useState(false); const [items, setItems] = useState([]); const dataUrl = 'https://api/test'; const cancelTokenRef = useRef(null); // 监听query变化,重置页码 useEffect(() => { if (page !== 1) { setPage(1); } }, [query]); // 触发请求并处理取消逻辑 useEffect(() => { if (cancelTokenRef.current) { cancelTokenRef.current.cancel('请求已取消:参数发生变化'); } cancelTokenRef.current = axios.CancelToken.source(); const fetchResults = async () => { setIsLoaded(false); setError(null); try { const response = await axios.get(dataUrl, { params: { query, page }, cancelToken: cancelTokenRef.current.token }); setItems(response.data); } catch (err) { if (!axios.isCancel(err)) { setError(err.message); } } finally { setIsLoaded(true); } }; fetchResults(); return () => { if (cancelTokenRef.current) { cancelTokenRef.current.cancel('请求已取消:组件卸载或参数变化'); } }; }, [query, page]); const handlePrevPage = () => { if (page > 1) { setPage(page - 1); } }; const handleNextPage = () => { setPage(page + 1); }; function Pagination() { return ( <div> <button onClick={handlePrevPage}> Prev </button> <span className="block py-1 px-2 font-bold text-white">{page}</span> <button onClick={handleNextPage} > Next </button> </div> ); } function List() { // 这里写你的列表渲染逻辑,比如遍历items return ( <ul> {items.map(item => ( <li key={item.id}>{item.title}</li> ))} </ul> ); } return ( <div> {error && <div className="text-red-500">Error: {error}</div>} {!isLoaded && <div className="text-gray-500">Loading...</div>} {isLoaded && <List />} <Pagination /> </div> ); }
关键要点总结
- 拆分职责:用两个
useEffect分别处理页码重置和请求触发,逻辑更清晰,避免耦合。 - 取消冗余请求:通过Axios的取消令牌,每次触发新请求前取消旧的未完成请求,既减少了不必要的API调用,又避免了竞态问题。
- 避免无意义更新:重置页码时加了
page !== 1的判断,防止query不变但页码本来就是1时的状态更新。
这样调整后,你就能实现:query变化时自动回到第一页,同时不会出现重复请求的问题啦!
内容的提问来源于stack exchange,提问作者demogorgorn
相关产品推荐
相关产品推荐

