0

所以我正在研究一个绘制中心和辐条图的功能(即我有一个中心 div,周围有其他 div 有 svg 线将所有外部 div 连接到中心)。您可以在此处看到一个稍微简化但仍然完整的代码沙箱示例。

为此,我制作了一个钩子,它将使用它们的 ref 获取每个 div 的边界框。它可以工作,但是触发次数过多并导致延迟。特别是当它有最大数量的盒子时

这是获取边界框的 ref。我相信这就是问题所在

import { useCallback, useLayoutEffect, useState } from "react";

const useBoundingBoxWithListener = (ref, childRef) => {
  const [bbox, setBbox] = useState({});

  const set = useCallback(
    () =>
      setBbox(ref && ref.current ? ref.current.getBoundingClientRect() : {}),
    [ref]
  );

  useLayoutEffect(() => {
    set();
    window.addEventListener("animationend", set);
    window.addEventListener("resize", set);

    return () => {
      window.removeEventListener("animationend", set);
      window.removeEventListener("resize", set);
    };
  }, [ref, set]);

  return { bbox, ref };
};

export { useBoundingBoxWithListener };

这是为每个 ref 使用边界框钩子的组件:

import React from "react";
import { locationEnum } from "./Spoke";
import { useBoundingBoxWithListener } from "./useBoundingBoxWithListener";

const useSpokes = () => {
  const middleLeftCard = React.useRef();
  const middleCard = React.useRef();
  const middleRightCard = React.useRef();
  const topLeftCard = React.useRef();
  const topMiddleCard = React.useRef();
  const topRightCard = React.useRef();
  const bottomLeftCard = React.useRef();
  const bottomMiddleCard = React.useRef();
  const bottomRightCard = React.useRef();
  const middleLeftSvgRef = React.useRef();
  const middleLeftFoRef = React.useRef();
  const middleRightSvgRef = React.useRef();
  const middleRightFoRef = React.useRef();
  const topLeftSvgRef = React.useRef();
  const topLeftFoRef = React.useRef();
  const topMiddleSvgRef = React.useRef();
  const topMiddleFoRef = React.useRef();
  const topRightSvgRef = React.useRef();
  const topRightFoRef = React.useRef();
  const bottomLeftSvgRef = React.useRef();
  const bottomLeftFoRef = React.useRef();
  const bottomMiddleSvgRef = React.useRef();
  const bottomMiddleFoRef = React.useRef();
  const bottomRightSvgRef = React.useRef();
  const bottomRightFoRef = React.useRef();

  const { bbox: middleLeftBox } = useBoundingBoxWithListener(middleLeftCard);
  const { bbox: middleBox } = useBoundingBoxWithListener(middleCard);
  const { bbox: middleRightBox } = useBoundingBoxWithListener(middleRightCard);
  const { bbox: topLeftBox } = useBoundingBoxWithListener(topLeftCard);
  const { bbox: topMiddleBox } = useBoundingBoxWithListener(topMiddleCard);
  const { bbox: topRightBox } = useBoundingBoxWithListener(topRightCard);
  const { bbox: bottomLeftBox } = useBoundingBoxWithListener(bottomLeftCard);
  const { bbox: bottomMiddleBox } = useBoundingBoxWithListener(
    bottomMiddleCard
  );
  const { bbox: bottomRightBox } = useBoundingBoxWithListener(bottomRightCard);

  const { bbox: middleLeftSvgBox } = useBoundingBoxWithListener(
    middleLeftSvgRef
  );
  const { bbox: middleRightSvgBox } = useBoundingBoxWithListener(
    middleRightSvgRef
  );
  const { bbox: topLeftSvgBox } = useBoundingBoxWithListener(topLeftSvgRef);
  const { bbox: topMiddleSvgBox } = useBoundingBoxWithListener(topMiddleSvgRef);
  const { bbox: topRightSvgBox } = useBoundingBoxWithListener(topRightSvgRef);
  const { bbox: bottomLeftSvgBox } = useBoundingBoxWithListener(
    bottomLeftSvgRef
  );
  const { bbox: bottomMiddleSvgBox } = useBoundingBoxWithListener(
    bottomMiddleSvgRef
  );
  const { bbox: bottomRightSvgBox } = useBoundingBoxWithListener(
    bottomRightSvgRef
  );

  const { bbox: middleLeftFoBox } = useBoundingBoxWithListener(middleLeftFoRef);
  const { bbox: middleRightFoBox } = useBoundingBoxWithListener(
    middleRightFoRef
  );
  const { bbox: topLeftFoBox } = useBoundingBoxWithListener(topLeftFoRef);
  const { bbox: topMiddleFoBox } = useBoundingBoxWithListener(topMiddleFoRef);
  const { bbox: topRightFoBox } = useBoundingBoxWithListener(topRightFoRef);
  const { bbox: bottomLeftFoBox } = useBoundingBoxWithListener(bottomLeftFoRef);
  const { bbox: bottomMiddleFoBox } = useBoundingBoxWithListener(
    bottomMiddleFoRef
  );
  const { bbox: bottomRightFoBox } = useBoundingBoxWithListener(
    bottomRightFoRef
  );

  const spokes = [
    {},
    {
      from: middleBox,
      to: topMiddleBox,
      fromRef: middleCard,
      toRef: topMiddleCard,
      start: locationEnum.topMiddle,
      end: locationEnum.bottomMiddle
    },
    {
      from: middleBox,
      to: bottomMiddleBox,
      fromRef: middleCard,
      toRef: bottomMiddleCard,
      start: locationEnum.bottomMiddle,
      end: locationEnum.topMiddle
    },
    {
      from: middleBox,
      to: middleLeftBox,
      fromRef: middleCard,
      toRef: middleLeftCard,
      start: locationEnum.middleLeft,
      end: locationEnum.middleRight
    },
    {
      from: middleBox,
      to: middleRightBox,
      fromRef: middleCard,
      toRef: middleRightCard,
      start: locationEnum.middleRight,
      end: locationEnum.middleLeft
    },
    {
      from: middleBox,
      to: topLeftBox,
      fromRef: middleCard,
      toRef: topLeftCard,
      start: locationEnum.topLeft,
      end: locationEnum.bottomRight
    },
    {
      from: middleBox,
      to: bottomRightBox,
      fromRef: middleCard,
      toRef: bottomRightCard,
      start: locationEnum.bottomRight,
      end: locationEnum.topLeft
    },
    {
      from: middleBox,
      to: topRightBox,
      fromRef: middleCard,
      toRef: topRightCard,
      start: locationEnum.topRight,
      end: locationEnum.bottomLeft
    },
    {
      from: middleBox,
      to: bottomLeftBox,
      fromRef: middleCard,
      toRef: bottomLeftCard,
      start: locationEnum.bottomLeft,
      end: locationEnum.topRight
    }
  ];

  const drawings = [
    {},
    {
      svgBox: topMiddleSvgBox,
      svgRef: topMiddleSvgRef,
      foreignObjectChildBox: topMiddleFoBox,
      foreignObjectChildRef: topMiddleFoRef
    },
    {
      svgBox: bottomMiddleSvgBox,
      svgRef: bottomMiddleSvgRef,
      foreignObjectChildBox: bottomMiddleFoBox,
      foreignObjectChildRef: bottomMiddleFoRef
    },
    {
      svgBox: middleLeftSvgBox,
      svgRef: middleLeftSvgRef,
      foreignObjectChildBox: middleLeftFoBox,
      foreignObjectChildRef: middleLeftFoRef
    },
    {
      svgBox: middleRightSvgBox,
      svgRef: middleRightSvgRef,
      foreignObjectChildBox: middleRightFoBox,
      foreignObjectChildRef: middleRightFoRef
    },
    {
      svgBox: topLeftSvgBox,
      svgRef: topLeftSvgRef,
      foreignObjectChildBox: topLeftFoBox,
      foreignObjectChildRef: topLeftFoRef
    },
    {
      svgBox: bottomRightSvgBox,
      svgRef: bottomRightSvgRef,
      foreignObjectChildBox: bottomRightFoBox,
      foreignObjectChildRef: bottomRightFoRef
    },
    {
      svgBox: topRightSvgBox,
      svgRef: topRightSvgRef,
      foreignObjectChildBox: topRightFoBox,
      foreignObjectChildRef: topRightFoRef
    },
    {
      svgBox: bottomLeftSvgBox,
      svgRef: bottomLeftSvgRef,
      foreignObjectChildBox: bottomLeftFoBox,
      foreignObjectChildRef: bottomLeftFoRef
    }
  ];

  return { spokes, drawings, middleCard };
};

export default useSpokes;

这是让 div 准备好显示的组件。

const MyView = ({ children }) => {
  const layoutClasses = [
    "subject",
    "topMiddle",
    "bottomMiddle",
    "middleLeft",
    "middleRight",
    "topLeft",
    "bottomRight",
    "topRight",
    "bottomLeft"
  ];
  const { spokes, drawings, middleCard } = useSpokes();

  return (
    <div className="layout">
      {children.map((c, i) => {
        return (
          <div
            key={i}
            ref={i === 0 ? middleCard : spokes[i].toRef}
            className={`${layoutClasses[i]} timing`}
          >
            {c}
            {i > 0 ? (
              <Spoke
                text={c.props?.subjectPredicate}
                foBox={drawings[i].foreignObjectChildBox}
                foRef={drawings[i].foreignObjectChildRef}
                {...spokes[i]}
                {...drawings[i]}
              />
            ) : (
              ""
            )}
          </div>
        );
      })}
    </div>
  );
};

这是实际显示所有内容的组件:

    <MyView>
      <div className="card"></div>
      <div className="card" subjectPredicate={"node"}></div>
      <div className="card" subjectPredicate={"node"}></div>
      <div className="card" subjectPredicate={"node"}></div>
      <div className="card" subjectPredicate={"node"}></div>
      <div className="card" subjectPredicate={"node"}></div>
      <div className="card" subjectPredicate={"node"}></div>
      <div className="card" subjectPredicate={"node"}></div>
      <div className="card" subjectPredicate={"node"}></div>
    </MyView>

我在整个示例console.logSpokes组件中添加了一个以显示它在控制台中实际被击中的次数(1000+)。如果你想分叉它并尝试直接使用它,你可以在上面的代码沙箱中看到所有东西一起工作。

4

2 回答 2

0

Wow I figured it out! The issue was that layout effect was too early in the lifecycle! Odd because i've usually header when you're dealing with painting things on the screen it should be done with layout but i guess in this case it makes sense because you want them to be painted to you can get the bounding box.

Something like the below. Only issue at the moment is that it seems like there is a memory leak to plug. I'll post an update if I find it:

Edit: well, the memory leak is proving very hard to plug... might have to pass but will update if i come up with something.

import { useCallback, useEffect, useState } from 'react';

const useBoundingBoxWithEventListeners = (ref) => {
  const [bbox, setBbox] = useState({});

  const set = useCallback(() => 
    setBbox(ref && ref.current ? ref.current.getBoundingClientRect() : {}), [ref]);

  useEffect(() => {
    set();
    window.addEventListener('resize', set);
    
    return () => {
      window.removeEventListener('resize', set);
    };
  }, [ref, set, bbox]);

  return {bbox, ref};
};


export { useBoundingBoxWithEventListeners };
于 2021-01-06T20:52:17.967 回答
0

我做了一个叉子给你看。

不过要解释一下:我用它包装了Spoke组件,React.memo并且我的重新渲染从 ~2k 下降到了 272。这并没有解决轻微的延迟/跳跃,所以我删除了您在父 div 上呈现的“计时”类名在MyView.js组件中。这使得线条渲染平滑,但“节点”框永久存在。根据您的描述,不能 100% 确定预期的功能是什么,只是存在不必要的延迟,但认为这可能足以让您走上正确的轨道,(似乎您的“计时”功能实际上并没有停止当你的盒子移动时,动画在线上运行,延迟只是保持它的可见性)。

同样由于某种原因, locationEnum 正在从它正在导入的地方导出,不确定是否是故意的?

于 2021-01-06T16:58:29.773 回答