React项目初始渲染重复加载6条Job数据问题排查
问题分析与解决方案:初始加载Hacker News职位数据重复12条
问题原因
StrictMode触发重复请求:React 18开发环境的StrictMode会自动执行两次挂载阶段的
useEffect,用于检测副作用问题。你的第一个useEffect(调用fetchJobId)会被执行两次,每次都获取职位ID列表并更新状态。由于第二个useEffect依赖jobId,每次jobId更新都会触发fetchJobData,而此时pages仍为0,导致两次加载第0-6条数据,最终jobData出现12条重复内容。hasMore状态判断逻辑错误:在fetchJobData中,你使用jobData.length判断是否还有更多数据,但setJobData是异步更新的,此时jobData仍是更新前的旧值,导致判断逻辑失效。正确的判断依据应该是总职位ID数量(jobId.length),而非已加载的数据长度。
修复方案
1. 避免fetchJobId重复执行
使用useRef标记是否已加载过职位ID列表,防止StrictMode下的重复请求:
const isJobIdLoaded = useRef(false); useEffect(() => { if (!isJobIdLoaded.current) { fetchJobId(); isJobIdLoaded.current = true; } }, []);
2. 修正hasMore判断逻辑
基于总职位ID数量判断是否还有更多数据:
const fetchJobData = async () => { // ... 其他代码不变 setHasMore((pages + 1) * No_Of_Pages < jobId.length); };
3. 优化第二个useEffect的触发条件
确保仅在初始加载(pages=0且jobData为空)或pages变化时执行fetchJobData:
useEffect(() => { if (jobId.length > 0) { if ((pages === 0 && jobData.length === 0) || pages > 0) { fetchJobData(); } } }, [pages, jobId, jobData]);
最终修复后的完整代码
import { useEffect, useState, useRef } from "react"; import "./App.css"; function App() { const [jobId, setJobId] = useState([]); const [jobData, setJobData] = useState([]); const [pages, SetPages] = useState(0); const [hasMore, setHasMore] = useState(true); const [loading, setLoading] = useState(false); const No_Of_Pages = 6; const isJobIdLoaded = useRef(false); useEffect(() => { if (!isJobIdLoaded.current) { fetchJobId(); isJobIdLoaded.current = true; } }, []); useEffect(() => { if (jobId.length > 0) { if ((pages === 0 && jobData.length === 0) || pages > 0) { fetchJobData(); } } }, [pages, jobId, jobData]); const fetchJobId = async () => { const response = await fetch( "https://hacker-news.firebaseio.com/v0/jobstories.json" ); const data = await response.json(); setJobId(data); }; const fetchJobData = async () => { setLoading(true); const JobrangeId = jobId.slice( pages * No_Of_Pages, (pages + 1) * No_Of_Pages ); const JobDataPromise = JobrangeId.map(async (id) => { const response = await fetch( `https://hacker-news.firebaseio.com/v0/item/${id}.json` ); const data = await response.json(); return data; }); const jobInfo = await Promise.all(JobDataPromise); setJobData((prevjob) => [...prevjob, ...jobInfo]); setHasMore((pages + 1) * No_Of_Pages < jobId.length); setLoading(false); }; return ( <div className=""> <div className="h-[500px] w-[100%] relative p-4 bg-gray-50 overflow-y-scroll"> <div className="flex flex-col w-[100%] h-[100%] items-center "> <p className="text-orange-400 font-bold text-2xl"> Hacker News Jobs Board </p> {jobData.length > 0 ? jobData.map((ele) => ( <div className="w-5/6 h-16 border-[1.5px] border-gray-300 m-3 p-1.5 bg-white" key={ele.id} > <div className="flex flex-col "> <div> <p className="font-bold text-black text-xl"> {ele?.title} </p> </div> <div className="flex justify-start gap-2"> <p className="font-normal text-base">By {ele?.by}</p> <p>-</p> <p>{new Date(ele?.time * 1000).toLocaleString()}</p> </div> </div> </div> )) : null} <div className="w-5/6 flex justify-start"> <button className="p-3 bg-orange-400 text-white rounded-md" onClick={() => SetPages(pages + 1)} disabled={!hasMore || loading} > {loading ? "Loading..." : "Load more"} </button> </div> </div> </div> </div> ); } export default App;
额外优化点:
- 将列表项
key从index改为ele.id,避免数据顺序变化导致的渲染异常 - 把时间戳转换为可读格式
- 按钮禁用条件添加
loading状态,防止重复点击
内容的提问来源于stack exchange,提问作者aman
相关产品推荐
相关产品推荐

