在 React 中更改 mousedown 和 mousemove 的宽度

Change width on mousedown and mousemove in React

我想构建一个挂钩来更改 mousemovemousedown 事件中元素的宽度。

我正在使用以下代码(实际上有效):

import React, { useState, useCallback, useEffect, useRef } from "https://cdn.skypack.dev/react";
import ReactDOM from "https://cdn.skypack.dev/react-dom";

const useMouseDelta = (initialWidth) => {
  const [dragging, setDragging] = useState(false);
  const [result, setResult] = useState(initialWidth);
  const origin = useRef(initialWidth);

  const handleMouseDown = useCallback((e) => {
    origin.current = e.clientX;
    setDragging(true);
  }, []);

  const handleMouseUp = useCallback((e) => {
    setDragging(false);
  }, []);

  const handleMouseMove = useCallback((e) => {
      if (!dragging) {
        return;
      }
      setResult(result + (e.clientX - origin.current));
    },
    [dragging]
  );

  useEffect(() => {
    window.addEventListener('mousedown', handleMouseDown);
    window.addEventListener('mouseup', handleMouseUp);
    window.addEventListener('mousemove', handleMouseMove);
    return () => {
      window.removeEventListener('mousedown', handleMouseDown);
      window.removeEventListener('mouseup', handleMouseUp);
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, [dragging]);

  return result;
};


const Test = () => {
  const width = useMouseDelta(400);
  return (
    <div className="container" style={{width: width}}>
      <div className="resize"></div>
    </div> 
  )
}

ReactDOM.render(<Test />, document.getElementById('root'));

但是,我在 ESLint 中看到了这个警告:

React Hook useCallback has a missing dependency: 'result'. Either include it or remove the dependency array. You can also do a functional update 'setResult(r => ...)' if you only need 'result' in the 'setResult' call

所以我改成:

setResult(r => r + (e.clientX - origin.current));

现在拖动不再按预期工作。

可在此处找到示例: CodePen example

在此 Effect 中,您可以像这样添加所有依赖项:

useEffect(() => {
    window.addEventListener('mousedown', handleMouseDown);
    window.addEventListener('mouseup', handleMouseUp);
    window.addEventListener('mousemove', handleMouseMove);
    return () => {
      window.removeEventListener('mousedown', handleMouseDown);
      window.removeEventListener('mouseup', handleMouseUp);
      window.removeEventListener('mousemove', handleMouseMove);
    };
  }, [dragging, handleMouseDown, handleMouseUp, handleMouseMove]);

在 handleMouseMove 中有点棘手。它是故意省略的,因此它只会看到组件的 'currentWidth' 而不是更新版本。 setResult 可以这样使用:

  const handleMouseMove = useCallback((e) => {
      if (!dragging) {
        return;
      }
      setResult((result) => result + (e.clientX - origin.current));
    },
    [dragging]
  );

然后warning就不会出来了,但是你会看到它总是看到更新后的值,会引起额外的加法。

一个解决方案是在 useMouseDelta 中以不同的状态存储当前的 witdth,然后可以将其添加为依赖项:

const useMouseDelta = (initialWidth: number) => {
  // The distance the mouse has moved since `mousedown`.
  const [dragging, setDragging] = useState(false);
  const [result, setResult] = useState(initialWidth);
  const [currentWidth, setCurrentWidth] = useState(initialWidth);

  // Original position independent of any dragging.  Updated when done dragging.
  const origin = useRef(initialWidth);

  const handleMouseDown = useCallback((e) => {
    origin.current = e.clientX;
    setDragging(true);
  }, []);

  const handleMouseUp = useCallback(
    (e) => {
      setDragging(false);
      setCurrentWidth(result);
    },
    [result]
  );

  const handleMouseMove = useCallback(
    (e) => {
      if (!dragging) {
        return;
      }
      setResult(() => currentWidth + e.clientX - origin.current);
    },
    [currentWidth, dragging]
  );

  useEffect(() => {
    window.addEventListener("mousedown", handleMouseDown);
    window.addEventListener("mouseup", handleMouseUp);
    window.addEventListener("mousemove", handleMouseMove);
    return () => {
      window.removeEventListener("mousedown", handleMouseDown);
      window.removeEventListener("mouseup", handleMouseUp);
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, [dragging, handleMouseDown, handleMouseUp, handleMouseMove]);

  return result;
};
  1. 您不需要存储 origin,但您需要在两个事件之间存储 previousClientXuseRef() and add just a change of clientX
  2. 你不需要存储 dragginguseState(), useRef() 就足够了。
  3. 您需要将所有处理程序添加到 useEffect() 中的依赖项列表中。

改进代码:

CodePen

import React, {
  useState,
  useCallback,
  useEffect,
  useRef
} from "https://cdn.skypack.dev/react";
import ReactDOM from "https://cdn.skypack.dev/react-dom";

const useMouseDelta = (initialWidth) => {
  const [result, setResult] = useState(initialWidth);
  const dragging = useRef(false);
  const previousClientX = useRef(0);

  const handleMouseMove = useCallback((e) => {
    if (!dragging.current) {
      return;
    }

    setResult((result) => {
      const change = e.clientX - previousClientX.current;
      previousClientX.current = e.clientX;
      return result + change;
    });
  }, []);

  const handleMouseDown = useCallback((e) => {
    previousClientX.current = e.clientX;
    dragging.current = true;
  }, []);

  const handleMouseUp = useCallback((e) => {
    dragging.current = false;
  }, []);

  useEffect(() => {
    window.addEventListener("mousedown", handleMouseDown);
    window.addEventListener("mouseup", handleMouseUp);
    window.addEventListener("mousemove", handleMouseMove);

    return () => {
      window.removeEventListener("mousedown", handleMouseDown);
      window.removeEventListener("mouseup", handleMouseUp);
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, [handleMouseDown, handleMouseUp, handleMouseMove]);

  return result;
};

const Test = () => {
  const width = useMouseDelta(400);

  return (
    <div className="container" style={{ width }}>
      <div className="resize" />
    </div>
  );
};

ReactDOM.render(<Test />, document.getElementById("root"));