You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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更新时会触发子组件重新创建定时器,在新旧定时器交替的间隙,旧状态可能还未被覆盖。
修复方案
  1. 使用唯一ID作为组件key
    将父组件中Item的key从index改为条目的唯一标识itemId,确保每个组件实例对应唯一的条目,避免React错误复用组件:
<Item 
  item={ item } 
  data={ data } 
  setData={ setData } 
  key={item.itemId} 
/>
  1. 初始化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";
});
  1. 优化数据更新逻辑,减少不必要的依赖
    在子组件中使用函数式更新修改data,避免依赖外部的data变量,从而减少useEffect的触发次数:
if (minutes <= 0 && seconds <= 0) {
  setData(prevData => prevData.filter(item => item.itemId !== itemID));
}

同时可以将data从useEffect的依赖数组中移除,只保留item和setData,避免data更新时频繁创建和销毁定时器。

内容的提问来源于stack exchange,提问作者zeropsi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 08:56:01