reactjs 使用引用数组而不是单个引用

pxy2qtax  于 2022-12-12  发布在  React
关注(0)|答案(3)|浏览(128)

bounty将在4天后过期。回答此问题可获得+400声望奖励。Kamran Asgari希望吸引更多人关注此问题。

我在一个map循环中使用了ref。我需要一个ref数组。问题是ref只针对列表中生成的最后一个元素。这里是我准备的示例,我需要在map循环中通过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>
  );
}

到代码沙箱的链接在这里是https://codesandbox.io/s/determined-wave-pfklec?file=/src/App.js

eulz3vhy

eulz3vhy1#

假设目标是使每个生成的元素都可以单独拖动,下面是一个示例,将一些ref切换为数组,并将pressed更改为number | boolean以传递index
legacyRefpressed的名称更改为handleRefspressedIndex,以反映其用例的差异。
分叉现场演示:codesandbox(已更新以省略使用useCallback
但是,应用了挂钩后,似乎每个元素(除了第一个元素)的可拖动区域都有限。
发布的示例在第三个可拖动项上也有这种行为,所以不确定这是否是钩子的意图。如果不是,可能需要调整可拖动项的实现以适合所有元素。
希望能起到一定的参考作用。

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>
  );
}
iqih9akk

iqih9akk2#

在useDraggable函数中,创建一个引用数组:

const refs = useRef([]);
  const unsubscribe = useRef();
  const legacyRef = useCallback((elem, index) => {
    refs.current[index] = elem;
    if (unsubscribe.current) {
      unsubscribe.current();
    }
    if (!elem) {
      return;
    }

在handleMouseMove中使用此数组:

if (!refs.current || !position.current) {
        return;
      }
      const pos = position.current;

      const elem = refs.current[index];
      position.current = onDrag({
[...]

我们的想法是使用数组索引将ref赋给每个元素。

wvt8vs2t

wvt8vs2t3#

您可以像这样使用ref。

let ref = [];

像这样使用它。
ref={(element) => (flatlistRef[index] = element)}
你也可以像这样使用map函数。
ref.map(async (item, index) => {})

相关问题