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

快速点击增减按钮时关联滚动容器的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.12 06:39:04