React useEffect拼接state触发无限循环的最佳解决方案
解决方案
你遇到的问题本质是对useState更新方式的使用场景不熟悉,根本不需要把list加入effect依赖数组——你在effect里用到list只是为了在更新状态时拿到之前的列表值做拼接,完全没必要因为这个值的变化重新触发请求。
直接用setState的函数式更新语法就能同时满足lint规则和避免无限循环,这也是官方推荐的标准写法:
function Component({ page }) { const [list, setList] = useState([]); useEffect(() => { // 补充竞态处理,避免快速切页时旧请求晚返回覆盖新数据 const abortController = new AbortController(); axios.get(`${[url]}${page}`, { signal: abortController.signal }) .then(newList => { // 函数式更新可以直接拿到上一次更新的最新state,不需要从外部作用域读取list setList(prevList => [...prevList, ...newList]); }) .catch(err => { // 忽略主动取消的请求,其余错误正常处理 if (err.name !== 'CanceledError') console.error(err); }); // 清理逻辑:page变更/组件卸载时取消未完成的旧请求 return () => abortController.abort(); }, [page]); // 依赖数组仅保留page,完全符合exhaustive-deps校验规则,无警告 return /* 列表渲染对应的JSX逻辑 */; }
为什么这个写法符合规范
- 满足lint校验要求:effect内部没有直接引用外部作用域的
list变量,依赖数组只需要包含实际触发effect重跑的page,不会触发任何规则警告 - 不会产生无限循环:effect的执行仅和
page绑定,list更新不会触发effect重跑 - 状态更新准确:函数式更新入参
prevList永远是最新的状态值,不会出现闭包缓存旧值导致列表拼接错误的问题
几个不推荐的反模式写法
- 不要直接加注释屏蔽exhaustive-deps校验,这种做法会掩盖真实的依赖缺失问题,后续迭代很容易出现闭包导致的旧值bug
- 不要用
useRef存储list绕开依赖检测,这会破坏React单向数据流的一致性,大幅提升后续维护成本 - 不要把请求逻辑拆到额外的自定义hook里但依然保留list依赖,本质还是没有解决状态更新时的闭包问题
内容的提问来源于stack exchange,提问作者Amir Rezvani
相关产品推荐
相关产品推荐

