React中结合setTimeout更新对象数组state实现逐个UI更新的方案
问题原因
原来的实现存在两个核心问题:
setItems是在所有定时器外部直接同步执行的,执行时所有项的has_x还是初始的false,后续定时器中修改的是已经被闭包捕获的临时数组副本,React状态无法感知这个变更,不会触发重渲染。- 直接使用捕获的初始状态快照修改,无法和后续的状态变更(比如用户中途点了某个单项的选中)联动,容易出现状态不同步的问题。
正确实现方案
直接使用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
相关产品推荐
相关产品推荐

