是什么让我的鼠标悬停和反应 setState 变慢?

Whats making my mouseover and react setState slow?

我创建的任何一种“点击并拖动”类型的动作感觉都...有点不稳定。就像是口吃一样。我知道当我控制台记录数据时,数据是事件的即时数据,但状态更新中的延迟使它看起来更慢。

这是一个非常简单的例子:

const TestMouseOver = () => {
  const [pos, setPos] = useState();

  const onMouseDown = (e) => {
    setPos({
      x: e.clientX + document.body.scrollLeft,
      y: e.clientY + document.body.scrollTop,
    })
  }
  const onMouseMove = (e) => {
    setPos({
      x: e.clientX + document.body.scrollLeft,
      y: e.clientY + document.body.scrollTop,
    })
  }
  return (
    <div
      onMouseDown={(event) => onMouseDown(event)}
      onMouseMove={(event) => onMouseMove(event)}
      onMouseUp={() => setPos(null)}
    >
      {pos && <MoveBox position={pos} />}
    </div>
  );
};

MoveBox 中,我采用 pos 并将 div 的绝对 css 设置为位置(样式组件)。那里没什么好看的。它 工作 但它很慢。

我是否应该尝试构建某种 lerp 或动画以转到最后知道的线以平滑位置更新?

MouseEvents 不受限制:每次您的鼠标移动时,它都会调用您的 setState 函数。这种情况每秒可能发生数百次,导致 React 经常重新渲染,这显然不是最佳选择。

在最大速度下,React 不应重新渲染超过 16 毫秒的每帧,以确保您网站上的 60 FPS 动画流畅。

例如,您需要使用 lodash.throttle 来限制这些事件,并使用 useCallback.

注册回调

示例代码:

const handleMouseMove = useCallback(lodash.throttle((e) => {
    setPos({
      x: e.clientX + document.body.scrollLeft,
      y: e.clientY + document.body.scrollTop,
    });
}, 16), []);

return (
    <div onMouseMove={handleMouseMove}>{...}</div>
)

请注意 16 作为 lodash.throttle 的第二个参数:它确保该函数不会比每 16 毫秒更频繁地被调用。

您应该考虑的另一种可能的方法是使用浏览器原生 api requestAnimationFrame

https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

这是针对您的特定案例的实施方式

const TestMouseOver = () => {
  const [pos, setPos] = useState();
  let ticking = false;

  function onMouseMove(event) {
    requestTick(event);
  }

  function onMouseDown(event) {
    requestTick(event);
  }

  function requestTick(event) {
    if (!ticking) {
      requestAnimationFrame(update(event));
    }
    ticking = true;
  }

  function update(e) {
    // reset the tick so we can capture next mouseevent
    ticking = false;

    setPos((prevState) => ({
      x: e.clientX + document.body.scrollLeft,
      y: e.clientY + document.body.scrollTop,
    }))
  }

  return (
    <div
      onMouseDown={(event) => onMouseDown(event)}
      onMouseMove={(event) => onMouseMove(event)}
      onMouseUp={() => setPos(null)}
    >
      {pos && <MoveBox position={pos} />}
    </div>
  );
};

优点:

  • 不需要任何外部库
  • 以帧为单位执行。所以,UI 行为很顺利。

缺点:

  • 不支持 IE9
  • 无法定义油门持续时间,因为它与帧率有关

工作原理: 基本上,使用变量和 requestAnimationFrame 方法,我们确保状态以帧更新的速率更新