快速点击增减按钮时关联滚动容器的React state出现异常如何修复?
问题根源
- 双向更新循环:修改
minutes触发useEffect修改滚动位置,滚动位置变化又触发onScroll回写minutes。快速点击按钮时滚动动画未完成,onScroll取到未完成的滚动位置计算出旧值,覆盖刚更新的minutes,对应第二个故障。 - 初始状态下滚动容器未完全渲染,
scrollableWidth为0,除以0得到NaN赋值给minutes,对应第一个故障。 - 按钮点击用了直接读取
minutes的写法,快速点击时存在闭包陷阱,可能取到旧的状态值。 - 原代码中
draggableElement未加const声明,会意外创建全局变量。
修复方案
1. 新增程序滚动锁,避免回写冲突
新增isProgrammaticScroll标记位,程序主动修改滚动位置时跳过onScroll的状态回写逻辑。
2. 新增边界判断,避免异常值
handleScroll中判断容器是否加载完成、滚动宽度是否合法,同时限制分钟数的取值范围。
3. 按钮点击改用函数式更新,规避闭包问题
直接读取minutes的写法在快速点击时可能拿到旧值,改用函数式更新始终获取最新状态。
修改后完整代码
const Container = ({ minutes, setMinutes}) => { // 补充const声明,新增程序滚动标记ref const draggableElement = useRef(null); const isProgrammaticScroll = useRef(false); const handleScroll = () => { // 程序触发的滚动直接跳过回写逻辑 if (isProgrammaticScroll.current) return; const container = draggableElement.current?.container?.current; // 容器未加载完成直接返回 if (!container) return; const scrollableWidth = container.scrollWidth - container.offsetWidth; // 滚动宽度为0时避免除以0得到NaN if (scrollableWidth <= 0) return; const amountScrolled = draggableElement.current.scrollLeft; const rangeValue = (amountScrolled / scrollableWidth) * 30; // 限制分钟数取值范围为0-30,避免异常值 const newMinutes = Math.max(0, Math.min(30, Math.round(rangeValue))); setMinutes(newMinutes); } useEffect(() => { const container = draggableElement.current?.container?.current; if (!container) return; // 标记为程序主动滚动,加锁 isProgrammaticScroll.current = true; container.scrollLeft = minutes * 20; // 滚动动画结束后解锁,预留100ms兼容不同浏览器滚动响应 setTimeout(() => { isProgrammaticScroll.current = false; }, 100); }, [minutes]); return ( <ScrollContainer className="container" ref={draggableElement} onScroll={handleScroll} id="scrolling-container" vertical={false} style={{ display: "grid", gridTemplateColumns: "repeat(100, 0.25em)", width: "4em", height: "50%", alignItems: "center", cursor: "grab", marginBottom: "1rem", }} > <Tick >1</Tick> <Tick >2</Tick> <Tick >3</Tick> <Tick >4</Tick> <Tick >5</Tick> //more ticks here </ScrollContainer> ); };
按钮部分修改为:
{/* 改用函数式更新,加边界限制 */} <AddButton onClick={()=> setMinutes(prev => Math.min(30, prev + 1))}></AddButton> <SubtractButton onClick={()=> setMinutes(prev => Math.max(0, prev - 1))}></SubtractButton>
内容的提问来源于stack exchange,提问作者Patorikku
相关产品推荐
相关产品推荐

