使用setInterval检测过期数据移除后出现渲染异常问题排查
问题描述
我有一个子组件Item,用于渲染并检查条目时间属性是否过期。其中data和setData是父组件Tab传递的props。通过setInterval可以正常实现倒计时功能,但当过滤过期条目并调用setData更新数据后,该条目会被移除,此时浏览器中新的首个条目会短暂显示0s,之后才恢复至正确的剩余时间。
子组件代码
function Item({ item, data, setData }) { const [endingTime, setEndingTime] = useState(); // 未初始化状态 useEffect(() => { const endTime = new Date(item.endTime); const itemID = item.itemId; const timeCheck = (endTime, itemID) => { let time = new Date(); let ending = Math.floor(parseInt(endTime) - (time.getTime()/1000)); let minutes = Math.floor(ending / 60); let seconds = ending % 60; if (minutes < 60 && minutes >=1 ) { setEndingTime(minutes + "m " + seconds + "s"); } else if (minutes === 0) { setEndingTime(seconds + "s"); } if (minutes <= 0 && seconds <= 0) { let newData = data.filter(item => item.itemId !== itemID); setData(newData); } } const intervalId = setInterval(() => { timeCheck(endTime.getTime()/1000, itemID) }, 1000); return () => clearInterval(intervalId); }, [item, data, setData]); return( <div className="item__time"> Time Left: { endingTime } </div> ) } export default Item;
父组件代码
function Tab({ setLoading }) { const [data, setData] = useState([]); const getItems = async () => { setLoading(true); try { const response = await fetch('local.api/endpoint'); const json = await response.json(); setData(json); } catch (error) { console.log("Error", error); } finally { setLoading(false); } } useEffect(() => { getItems(); }, []); return( <div className="tab"> {data.length > 0 && data.map((item, index) => { return( <Item item={ item } data={ data } setData={ setData } key={`item__${ index }`} /> ) }) } </div> ) } export default Tab;
原因分析
- 组件复用导致旧状态残留:父组件给
Item用了数组index作为key,当第一个过期条目被移除后,原第二个条目会变成新的第一个,React会复用原来第一个条目的组件实例。而这个复用的组件中endingTime状态还保留着之前过期条目的0s值,直到新的定时器触发更新,才会显示正确时间,因此出现短暂的0s。 - 状态未初始化:子组件的
endingTime没有设置初始值,首次渲染或组件复用时,状态值可能为undefined或旧组件残留的0s,导致渲染异常。 - 数据依赖引发不必要的重渲染:子组件
useEffect依赖了data,父组件data更新时会触发子组件重新创建定时器,在新旧定时器交替的间隙,旧状态可能还未被覆盖。
修复方案
- 使用唯一ID作为组件key
将父组件中Item的key从index改为条目的唯一标识itemId,确保每个组件实例对应唯一的条目,避免React错误复用组件:
<Item item={ item } data={ data } setData={ setData } key={item.itemId} />
- 初始化
endingTime状态
在useState中直接计算初始的剩余时间,确保组件渲染时就有正确的初始值:
const [endingTime, setEndingTime] = useState(() => { const endTime = new Date(item.endTime); const currentTime = new Date().getTime() / 1000; let remaining = Math.floor(parseInt(endTime.getTime() / 1000) - currentTime); const minutes = Math.floor(remaining / 60); const seconds = remaining % 60; if (minutes >= 1 && minutes < 60) { return `${minutes}m ${seconds}s`; } else if (minutes === 0) { return `${seconds}s`; } return "0s"; });
- 优化数据更新逻辑,减少不必要的依赖
在子组件中使用函数式更新修改data,避免依赖外部的data变量,从而减少useEffect的触发次数:
if (minutes <= 0 && seconds <= 0) { setData(prevData => prevData.filter(item => item.itemId !== itemID)); }
同时可以将data从useEffect的依赖数组中移除,只保留item和setData,避免data更新时频繁创建和销毁定时器。
内容的提问来源于stack exchange,提问作者zeropsi
相关产品推荐
相关产品推荐

