React useEffect Hook 实现未知总页数API全量请求的最佳方案
现有代码卡死的根因
- 缺少依赖数组的
useEffect会在组件每一次重渲染时都重新执行 - Effect内部写了同步
while循环,这个循环不会等待异步fetch的返回结果,会一直占用浏览器主线程,直接造成页面卡死 res.json()是异步Promise,代码没有等待它的resolve结果,直接把Promise对象传给了people.concat,根本拿不到真实的人物数据- 状态更新直接使用了闭包捕获的旧state值,存在竞态更新的问题
核心疑问解答
- 完全不适合在useEffect的首次执行流程里跑完全部分页请求:多页请求本身是异步串行的非阻塞流程,强行把所有请求塞进一次同步执行的effect逻辑里,既违背JS异步执行的基本特性,也会完全脱离React的状态渲染周期,必然引发死循环、内存泄漏、重复请求的问题。正确的通用范式是把单页请求拆为独立执行单元,由状态变更驱动每一次请求的发起。
- 必须将所有影响请求逻辑的动态变量传入useEffect的依赖数组:你实现的分页逻辑本质是通过
page自增推进请求进度,当接口返回404时通过gotEveryone状态终止请求。这种逻辑完全依赖「依赖值变化触发effect重跑」的内置机制,漏写依赖会直接导致请求逻辑失控,和你现在遇到的卡死问题直接相关。
可用的调整后实现
import React, { useState, useEffect } from 'react'; function GetAllPeople() { const [page, setPage] = useState(1) const [people, setPeople] = useState([]); const [gotEveryone, setGotEveryone] = useState(false) const [error, setError] = useState(null) useEffect(() => { // 已经拉完全部数据/出现错误时,直接终止当前effect逻辑,不发请求 if (gotEveryone || error) return // 定义单次页请求逻辑 const fetchPage = async () => { try { const res = await fetch(`https://swapi.dev/api/people/?page=${page}`) if (res.ok) { // 等待json解析完成,拿取真实响应数据 const response = await res.json() // 用函数式更新state,避免闭包拿到旧的people值 setPeople(prevPeople => prevPeople.concat(response.results)) // 页码自增,触发下一次effect执行、拉取下一页 setPage(prevPage => prevPage + 1) } else if (res.status === 404) { // 超过总页数的请求会返回404,标记全部数据拉取完成 setGotEveryone(true) } else { setError(res.status) } } catch (e) { setError(e.message) } } fetchPage() }, [page, gotEveryone, error]) // 把所有影响effect执行的依赖显式声明 return ( <div> {error && <div>请求出错:{error}</div>} {gotEveryone && <div>全部人物数据加载完成</div>} <ul> {people.map((person, index) => <li key={index}> {person.name} </li> )} </ul> </div> ) } export default GetAllPeople;
内容的提问来源于stack exchange,提问作者C. Ball
相关产品推荐
相关产品推荐

