使用 Ref 数组代替单个 Ref

Cam*_*n A 5 javascript draggable reactjs react-hooks use-ref

我在地图循环内使用 ref 。我需要一个引用数组问题是引用仅针对列表中生成的最后一个元素这是我准备的示例,我需要通过引用列表在地图循环内的所有生成元素上运行自定义钩子

我正在寻找一种不引入另一个组件的方法

import React, { useRef, useState, useEffect, useCallback } from "react";

/// throttle.ts
export const throttle = (f) => {
  let token = null,
    lastArgs = null;
  const invoke = () => {
    f(...lastArgs);
    token = null;
  };
  const result = (...args) => {
    lastArgs = args;
    if (!token) {
      token = requestAnimationFrame(invoke);
    }
  };
  result.cancel = () => token && cancelAnimationFrame(token);
  return result;
};

const id = (x) => x;
const useDraggable = ({ onDrag = id } = {}) => {
  const [pressed, setPressed] = useState(false);

  const position = useRef({ x: 0, y: 0 });
  const ref = useRef();

  const unsubscribe = useRef();
  const legacyRef = useCallback((elem) => {
    ref.current = elem;
    if (unsubscribe.current) {
      unsubscribe.current();
    }
    if (!elem) {
      return;
    }
    const handleMouseDown = (e) => {
      e.target.style.userSelect = "none";
      setPressed(true);
    };
    elem.addEventListener("mousedown", handleMouseDown);
    unsubscribe.current = () => {
      elem.removeEventListener("mousedown", handleMouseDown);
    };
  }, []);

  useEffect(() => {
    if (!pressed) {
      return;
    }

    const handleMouseMove = throttle((event) => {
      if (!ref.current || !position.current) {
        return;
      }
      const pos = position.current;

      const elem = ref.current;
      position.current = onDrag({
        x: pos.x + event.movementX,
        y: pos.y + event.movementY
      });
      elem.style.transform = `translate(${pos.x}px, ${pos.y}px)`;
    });
    const handleMouseUp = (e) => {
      e.target.style.userSelect = "auto";
      setPressed(false);
    };

    document.addEventListener("mousemove", handleMouseMove);
    document.addEventListener("mouseup", handleMouseUp);
    return () => {
      handleMouseMove.cancel();
      document.removeEventListener("mousemove", handleMouseMove);
      document.removeEventListener("mouseup", handleMouseUp);
    };
  }, [pressed, onDrag]);

  return [legacyRef, pressed];
};

/// example.ts
const quickAndDirtyStyle = {
  width: "200px",
  height: "200px",
  background: "#FF9900",
  color: "#FFFFFF",
  display: "flex",
  justifyContent: "center",
  alignItems: "center"
};

const DraggableComponent = () => {
  const handleDrag = useCallback(
    ({ x, y }) => ({
      x: Math.max(0, x),
      y: Math.max(0, y)
    }),
    []
  );

  const [ref, pressed] = useDraggable({
    onDrag: handleDrag
  });

  return (
    <>
      {[1, 2, 3].map((el, i) => (
        <div key={"element" + i} ref={ref} style={quickAndDirtyStyle}>
          <p>{pressed ? "Dragging..." : "Press to drag"}</p>
        </div>
      ))}
    </>
  );
};

export default function App() {
  return (
    <div className="App">
      <DraggableComponent />
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

Codesandbox 的链接位于 https://codesandbox.io/s/dependent-wave-pfklec?file=/src/App.js

Joh*_* Li 3

假设目标是使每个生成的元素可单独拖动,这里是一个示例,将一些元素切换ref为数组,并更改pressednumber | boolean传递index.

legacyRef将和的名称更改pressedhandleRefspressedIndex以反映其用例的差异。

分叉现场演示:codesandbox(已更新以省略 的使用useCallback

然而,应用钩子后,似乎每个元素(第一个元素除外)都有一个有限的可拖动区域。

发布的示例在第三个可拖动项目上也有此行为,因此不确定这是否是挂钩的意图。如果不是,也许需要调整draggable的实现以适合所有元素。

希望本文能有所帮助,作为参考。

import React, { useRef, useState, useEffect } from "react";

/// throttle.ts
export const throttle = (f) => {
  let token = null,
    lastArgs = null;
  const invoke = () => {
    f(...lastArgs);
    token = null;
  };
  const result = (...args) => {
    lastArgs = args;
    if (!token) {
      token = requestAnimationFrame(invoke);
    }
  };
  result.cancel = () => token && cancelAnimationFrame(token);
  return result;
};

const id = (x) => x;
const useDraggable = ({ onDrag = id } = {}) => {
  const [pressedIndex, setPressedIndex] = useState(false);
  const positions = useRef([]);
  const refs = useRef([]);
  const unsubscribes = useRef([]);

  const handleRefs = (elem, i) => {
    if (!elem) {
      return;
    }
    refs.current[i] = elem;
    if (!positions.current[i]) positions.current[i] = { x: 0, y: 0 };
    if (unsubscribes.current[i]) {
      unsubscribes.current[i]();
    }
    const handleMouseDown = (e) => {
      e.target.style.userSelect = "none";
      setPressedIndex(i);
    };
    elem.addEventListener("mousedown", handleMouseDown);
    unsubscribes.current[i] = () => {
      elem.removeEventListener("mousedown", handleMouseDown);
    };
  };

  useEffect(() => {
    if (!pressedIndex && pressedIndex !== 0) {
      return;
    }

    const handleMouseMove = throttle((event) => {
      if (
        !refs.current ||
        refs.current.length === 0 ||
        !positions.current ||
        positions.current.length === 0
      ) {
        return;
      }

      const pos = positions.current[pressedIndex];
      const elem = refs.current[pressedIndex];

      positions.current[pressedIndex] = onDrag({
        x: pos.x + event.movementX,
        y: pos.y + event.movementY
      });

      elem.style.transform = `translate(${pos.x}px, ${pos.y}px)`;
    });

    const handleMouseUp = (e) => {
      e.target.style.userSelect = "auto";
      setPressedIndex(false);
    };

    document.addEventListener("mousemove", handleMouseMove);
    document.addEventListener("mouseup", handleMouseUp);
    return () => {
      handleMouseMove.cancel();
      document.removeEventListener("mousemove", handleMouseMove);
      document.removeEventListener("mouseup", handleMouseUp);
    };
  }, [pressedIndex, onDrag]);

  return [handleRefs, pressedIndex];
};

/// example.ts
const quickAndDirtyStyle = {
  width: "200px",
  height: "200px",
  background: "#FF9900",
  color: "#FFFFFF",
  display: "flex",
  justifyContent: "center",
  alignItems: "center"
};

const DraggableComponent = () => {
  const handleDrag = ({ x, y }) => ({
    x: Math.max(0, x),
    y: Math.max(0, y)
  });

  const [handleRefs, pressedIndex] = useDraggable({
    onDrag: handleDrag
  });

  return (
    <>
      {[1, 2, 3].map((el, i) => (
        <div
          key={"element" + i}
          ref={(element) => handleRefs(element, i)}
          style={quickAndDirtyStyle}
        >
          <p>{pressedIndex === i ? "Dragging..." : "Press to drag"}</p>
        </div>
      ))}
    </>
  );
};

export default function App() {
  return (
    <div className="App">
      <DraggableComponent />
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)