简体   繁体   English

使用 Refs 数组而不是单个 Ref

[英]Using array of Refs intead of single Ref

I'm using ref inside a map loop.我在地图循环中使用 ref。 I need an array of refs The problem is the ref only target the last element generated in the list here is the example I prepared, I need to have the custom hook run on all generated elements inside the map loop by a list of ref我需要一个 refs 数组问题是 ref 只针对列表中生成的最后一个元素这里是我准备的示例,我需要通过 ref 列表在 map 循环内的所有生成元素上运行自定义挂钩

I'm looking for a way without introducing another Component我正在寻找一种不引入另一个组件的方法

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

a link to a codesandbox is here https://codesandbox.io/s/determined-wave-pfklec?file=/src/App.js codesandbox 的链接在这里https://codesandbox.io/s/determined-wave-pfklec?file=/src/App.js

Assuming that the goal is to make each generated element draggable individually, here is an example by switching some ref to arrays, and changed pressed to number | boolean假设目标是使每个生成的元素都可以单独拖动,下面是一个示例,将一些ref切换为数组,并将pressed更改为number | boolean number | boolean to pass an index .传递indexnumber | boolean

Changed the names of legacyRef and pressed to handleRefs and pressedIndex to reflect the difference in their use case.legacyRefpressed的名称更改为handleRefspressedIndex以反映它们在用例中的差异。

Forked live demo on: codesandbox (updated to omit the use of useCallback )codesandbox上分叉现场演示(更新为省略useCallback的使用)

However, with the hook applied, it seems that each element (except the first one) has a limited draggable area.但是,应用挂钩后,似乎每个元素(第一个元素除外)的可拖动区域都是有限的。

The posted example also has this behavior on the third draggable item, so not sure if this is intended by the hook.发布的示例在第三个可拖动项上也有此行为,因此不确定这是否是挂钩有意为之。 If not, perhaps the implement of draggable need to be adjusted to be fit for all elements.如果不是,也许需要调整 draggable 的实现以适合所有元素。

Hope that this could help as a reference.希望这可以作为参考。

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

In useDraggable function, create an Array of refs:在 useDraggable 函数中,创建一个引用数组:

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

in handleMouseMove use this array:在 handleMouseMove 使用这个数组:

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

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

The idea is to use the array index to assign the ref to each element.这个想法是使用数组索引将ref分配给每个元素。 jHope it helps j希望有帮助

Instead of trying to declare an array of refs try rendering a component while iterating over the array and declare single ref in that component.与其尝试声明一个引用数组,不如尝试在遍历数组时渲染一个组件,并在该组件中声明单个引用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM