React中useEffect调用函数为何生成重复Pokemon数据?
问题原因与解决方案
核心原因
React 18及以上版本的开发模式中,StrictMode会自动重复执行无依赖的useEffect回调(以及其他部分生命周期函数),目的是帮你检测潜在的副作用bug。你的代码里:
- useEffect依赖为空数组,所以在开发环境会被执行两次
- 每次执行都会调用
getAllPokemons,而第一次调用时loadPoke还是初始的https://pokeapi.co/api/v2/pokemon?limit=20,两次调用都会拉取前20个Pokemon并添加到allPokemons数组,直接导致重复项。
而按钮触发时,每次点击只执行一次getAllPokemons,且第一次执行后loadPoke已经被更新为下一页的接口地址,后续点击只会添加新的Pokemon,不会重复。
另外你的getAllPokemons里还有个潜在问题:用forEach遍历异步操作,forEach不会等待异步任务完成,会导致多个setAllPokemons被无序调用,即使没有StrictMode的问题,也可能出现数据顺序混乱或不必要的重渲染。
修复方案
方案1:添加初始化标记,阻止重复执行
添加一个状态标记是否已经完成初始加载,避免useEffect重复触发时再次调用接口:
const PokeListPage = () => { const [layoutToggle, setLayoutToggle] = useState(false); const [allPokemons, setAllPokemons] = useState([]); const [loadPoke, setLoadPoke] = useState( "https://pokeapi.co/api/v2/pokemon?limit=20" ); // 添加初始化加载标记 const [isInitLoaded, setIsInitLoaded] = useState(false); useEffect(() => { if (!isInitLoaded) { getAllPokemons(); setIsInitLoaded(true); } }, [isInitLoaded]); const getAllPokemons = async () => { const res = await fetch(loadPoke); const data = await res.json(); setLoadPoke(data.next); // 改用Promise.all批量获取,解决forEach异步遍历的问题 const pokemonDataList = await Promise.all( data.results.map(async (pokemon) => { const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon.name}`); return res.json(); }) ); // 一次性更新数组,减少重渲染次数 setAllPokemons(prev => [...prev, ...pokemonDataList]); }; // 其他组件逻辑... };
方案2:临时禁用StrictMode(仅测试用)
如果只是临时验证问题,可以在项目入口文件(比如index.js)中去掉<StrictMode>的包裹,但不推荐长期使用——StrictMode能帮你提前发现很多隐蔽的bug。
额外说明
用Promise.all替代forEach异步遍历的好处:
- 所有请求并行发起,提升加载速度
- 等待所有请求完成后一次性更新状态,减少组件重渲染次数
- 保证添加到数组的Pokemon顺序和接口返回的一致
内容的提问来源于stack exchange,提问作者RobertoMancini
相关产品推荐
相关产品推荐

