React+Firebase中数组持续复制引发BUG的问题求助
问题分析与修复方案
核心错误点
- 数组嵌套问题:
toggleActive函数中更新appState时,错误使用[...appState, arrayCopy],将修改后的完整数组作为单个元素追加到原数组中,导致数组嵌套层级不断加深。 - 状态更新逻辑冗余:
setDisabled通过遍历调用toggleActive触发多次状态更新,且依赖旧的appState快照,容易引发状态不同步。 - 计时器重置逻辑隐患:
setTimeLeft回调中setDisabled() || 15的写法依赖无返回值的函数,逻辑不严谨。 - 样式判断索引不匹配:
toggleActiveStyles和toggleActiveStylesBtns用selectedItems的索引去匹配appState元素,但selectedItems是随机筛选的,索引无法对应正确元素。 - 初始
selectedItems无效:初始化selectedItems时appState为空,导致初始值错误。
修复后的代码
const CurrentEventsItem = () => { const [timeLeft, setTimeLeft] = useState(15); const [appState, changeState] = useState([]); const [selectedItems, setSelectedItems] = useState([]); const getPadTime = (time) => time.toString().padStart(2, "0"); const minutes = getPadTime(Math.floor(timeLeft / 60)); const seconds = getPadTime(timeLeft - minutes * 60); // 计时器逻辑:到期后重置状态并恢复计时 useEffect(() => { const interval = setInterval(() => { setTimeLeft((prevTime) => { if (prevTime >= 1) { return prevTime - 1; } else { setDisabled(); return 15; } }); }, 1000); return () => clearInterval(interval); }, []); // 随机筛选元素(先复制原数组再打乱,避免修改原数据) const getRandomElements = (array, count) => { const shuffled = [...array].sort(() => 0.5 - Math.random()); return shuffled.slice(0, count); }; // 加载初始数据 useEffect(() => { getCurrentEvents(); }, []); const getCurrentEvents = async() => { const data = await ProductDataService.getAllCurrentEvents(); const events = data.docs.map((doc) => ({...doc.data(), id: doc.id, statusItem: doc.data().statusItem ?? false})); changeState(events); // 数据加载完成后初始化选中项 setSelectedItems(getRandomElements(events, 3)); }; // 计时结束后刷新选中项 useEffect(() => { if (timeLeft === 0 && appState.length > 0) { setSelectedItems(getRandomElements(appState, 3)); } }, [timeLeft, appState]); // 根据item的状态返回样式(通过id匹配appState中的元素) const toggleActiveStyles = (item) => { const target = appState.find(o => o.id === item.id); return target?.statusItem ? "current__events__hot-price disabled" : "current__events__hot-price"; }; const toggleActiveStylesBtns = (item) => { const target = appState.find(o => o.id === item.id); return target?.statusItem ? "current__events__btn-green disabled" : "current__events__btn-green"; }; // 切换单个项的禁用状态 const toggleActive = (item) => { changeState(prevState => { return prevState.map(o => { if (o.id === item.id) { return {...o, statusItem: !o.statusItem}; } return o; }); }); }; // 恢复所有已禁用的项 const setDisabled = () => { changeState(prevState => { return prevState.map(o => { if (o.statusItem) { return {...o, statusItem: false}; } return o; }); }); }; return ( <> <div className='current__events__wrapper'> {selectedItems.map((item) => <div className="current__events__hot-price__item" key={item.id}> <div className={toggleActiveStyles(item)}> <h5 className="current__events__card-title__large">Hot Price</h5> </div> <div className="current__events__image"> <img src={item.avatar} alt='user' className="rounded-circle" width='75' height='75'/> </div> <div className="current__events__info"> <h4 className="current__events__title__middle">{item.title}</h4> </div> <div className="current__events__timer"> <span>{minutes}</span> <span>:</span> <span>{seconds}</span> </div> <button className={toggleActiveStylesBtns(item)} onClick={() => toggleActive(item)} disabled={item.statusItem} > MAKE TURN </button> </div> )} </div> </> ) }
关键修复说明
- 数组嵌套修复:
toggleActive改用函数式更新,直接返回修改后的数组,避免将数组作为元素追加。 - 批量状态更新:
setDisabled通过一次状态更新完成所有项的恢复,避免多次触发重渲染和状态不同步。 - 计时器逻辑优化:拆分重置逻辑,先执行
setDisabled再返回15,逻辑更清晰。 - 样式判断修正:通过
item.id匹配appState中的元素,不再依赖索引,确保样式判断准确。 - 初始化逻辑调整:在
getCurrentEvents加载完成后再设置selectedItems,避免初始值为空;同时给statusItem设置默认值,防止undefined。 - 随机函数优化:打乱数组前先复制原数组,避免修改原
appState。
内容的提问来源于stack exchange,提问作者Eugene Kliuchnikov
相关产品推荐
相关产品推荐

