React useEffect无依赖变化死循环、空依赖不渲染排查
问题根因说明
你遇到的问题分别对应代码层面的两类错误,和React渲染机制、异步逻辑写法直接相关:
1. 初始版本的无限循环问题
- 你将状态
dates放在了useEffect的依赖数组中,而effect内部执行setDates(values)会直接更新dates状态,触发组件重渲染。重渲染完成后useEffect会对比依赖值,发现dates和上一次渲染的引用不一致,就会再次执行effect逻辑,再次触发setDates,最终形成「执行effect→更新状态→重渲染→再执行effect」的死循环。
2. 空依赖版本不渲染内容的问题
这里有两个独立bug,共同导致了渲染为空的结果:
- 异步函数写法错误:你定义的
getAllFilledJournauxDates存在异步逻辑提前返回的问题。代码中你没有等待getAllJournaux()的Promise执行完成就直接return了dates数组,此时.then里遍历处理日期的逻辑还没执行,返回的永远是空数组。等后续异步请求完成往dates里push值时,函数早就把空值返回给调用方了。 - 普通变量无法跨渲染周期存值:你在组件顶层定义的
let values = []会在每次组件重渲染时被重新初始化为空数组。useEffect里的异步回调给values赋值时,当前渲染周期已经结束,下一次渲染又会把values重置为空,所以你在effect外部打印values永远是空值。 - 额外说明:你看到useEffect日志打印2次是React 18严格模式下开发环境的预期行为,目的是帮你排查未做清理的副作用,生产环境构建后只会执行1次,不是代码bug。
修复方案
第一步:修复异步数据获取函数
修正异步逻辑,等待接口返回后再处理日期、返回结果,同时修复原有日期格式化里月份少1的问题:
export async function getAllFilledJournauxDates() { // 等待接口请求完成再执行后续逻辑 const response = await getAllJournaux(); const dates = Object.keys(response).map(element => { const rawDate = new Date(response[element].date); // 注意getMonth返回0-11,对应实际1-12月,需要+1,补前导零避免格式错乱 const year = rawDate.getFullYear(); const month = String(rawDate.getMonth() + 1).padStart(2, '0'); const day = String(rawDate.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }) return dates; }
第二步:修复组件逻辑
去掉冗余的顶层values变量,拉取到数据后直接更新状态,空依赖数组保证只在组件首次挂载时拉取一次数据,列表渲染补充必填的key属性:
import { useState, useEffect } from 'react'; export default function Test() { const [dates, setDates] = useState([]); useEffect(() => { getAllFilledJournauxDates().then(response => { setDates(response); }); }, []); return( <div> {dates.map((date, index) => ( <div key={index}>{date}</div> ))} </div> ); }
注意事项
- 不要把effect内部会更新的状态放到依赖数组里,除非你明确需要监听该状态变化重新执行effect,否则会触发无限重渲染。
- 组件函数每次重渲染都会从头执行所有顶层代码,普通
let/const变量会被重新初始化,无法保存跨渲染周期的值,这类持久化数据请存在state或useRef中。 - 写async函数时,所有依赖异步结果的逻辑都要放在
await之后或者.then回调中,避免出现提前返回空值的问题。 - JS中
Date.prototype.getMonth()的返回值范围是0-11,直接使用会比实际月份小1,格式化时记得+1。
内容的提问来源于stack exchange,提问作者AWEMA Agence Web Marketing
相关产品推荐
相关产品推荐

