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

React中结合setTimeout更新对象数组state实现逐个UI更新的方案

问题原因

原来的实现存在两个核心问题:

  1. setItems 是在所有定时器外部直接同步执行的,执行时所有项的has_x还是初始的false,后续定时器中修改的是已经被闭包捕获的临时数组副本,React状态无法感知这个变更,不会触发重渲染。
  2. 直接使用捕获的初始状态快照修改,无法和后续的状态变更(比如用户中途点了某个单项的选中)联动,容易出现状态不同步的问题。

正确实现方案

直接使用React状态更新的函数式写法即可,不需要调用子组件ref,完全符合单向数据流规范:

基础实现

const checkAllItems = () => {
  items.forEach((item, index) => {
    // 按下标错开定时器触发时间,间隔250ms可自行调整到200-300ms区间
    setTimeout(() => {
      // 函数式更新能拿到最新的状态快照,不会有闭包问题
      setItems(prevItems => {
        return prevItems.map((prevItem, i) => {
          if (i === index) {
            // 此处可以插入你的业务高开销处理逻辑
            return {...prevItem, has_x: true};
          }
          return prevItem;
        })
      })
    }, index * 250)
  })
}

优化版(防重复点击+内存泄漏)

如果需要处理重复点击全选、或者组件中途卸载的场景,加一层定时器清理逻辑即可:

import { useState, useRef, useEffect } from 'react';

// 组件内代码
const [items, setItems] =  useState([ { key: 1, has_x: false }, { key: 2, has_x: false }, { key: 3, has_x: false } ]);
// 存储所有定时器ID
const timerIdsRef = useRef([]);

const checkAllItems = () => {
  // 先清空之前的定时器,避免重复点击触发多组更新
  timerIdsRef.current.forEach(id => clearTimeout(id));
  timerIdsRef.current = [];

  items.forEach((item, index) => {
    const timerId = setTimeout(() => {
      setItems(prevItems => 
        prevItems.map((prevItem, i) => 
          i === index ? {...prevItem, has_x: true} : prevItem
        )
      );
    }, index * 250);
    timerIdsRef.current.push(timerId);
  })
}

// 组件卸载时清空所有未触发的定时器
useEffect(() => {
  return () => timerIdsRef.current.forEach(id => clearTimeout(id));
}, [])

实现原理

每次定时器触发时,通过setItems的函数式更新参数拿到当前最新的items状态,只修改对应下标的单项,返回新的数组触发React重渲染,对应子组件拿到新的hasX属性就会自动更新UI,正好实现逐项更新、每两项间隔固定时间的效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 04:48:02