【发布时间】:2021-08-17 00:17:14
【问题描述】:
首先,code sandbox 演示问题。
我们有 2 个组件 - DraggableBox 是 react-draggable 的包装器,SimpleArrow 是 react-xarrows 的简化版本(我是作者)。
我们可以看到一个视觉错误,这是因为 DraggableBox 和 SimpleArrow 都基于相同的 DOM 框架(以及相同的反应渲染阶段 - useEffect)更新它们的状态,而 SimpleArrow 应该基于DraggableBox的位置。
如果我们对“SimpleArrow”上的每个渲染强制执行 2 次渲染,然后在第 2 次渲染时“SimpleArrow”将读取更新后的“DraggableBox”位置,我们可以解决这个问题。我们可以通过编写自定义钩子来强制进行 2 次渲染:
export const useMultipleRenders = (renders = 2, effect = useEffect) => {
const [, setRender] = useState({});
const reRender = () => setRender({});
const count = useRef(0);
effect(() => {
if (count.current != renders) {
reRender();
count.current += 1;
} else {
count.current = 0;
}
});
};
现在我们将在SimpleArrow 上消耗useMultipleRenders() 并且视觉故障将得到修复。
here's 带有修复程序的代码沙箱。你可以看到这个丑陋的解决方法有效。
这实际上在 React 中访问 DOM 时一直发生。 您在渲染期间使用 ref (useRef) 值访问 dom,在此渲染期间,您只能访问 dom 中当前的内容,这是上一次渲染的结果,但您实际上需要当前渲染!
例如,在SimpleArrow 中,我在svg 的内部使用getBoundingClientRect 来确定svg 的高度和宽度:
const SimpleArrow = (props) => {
const svgInnersRef = useRef<SVGGElement>(null);
const {
width: gWidth,
height: gHeight
} = svgInnersRef.current?.getBoundingClientRect() ?? { width: 0, height: 0 };
return (
<svg
width={gWidth}
height={gHeight}
// ...
>
<g ref={svgInnersRef}>
{/* ... */}
</g>
</svg>
);
};
但为了确保高度和宽度得到更新,我必须双重渲染,以便在最后一次渲染时获得正确的尺寸。
另一件事是useMultipleRenders 的实现并不安全,因为它会在渲染期间更改参考值。 React 核心成员 claims 在渲染期间设置 ref 值是不安全的。
我能做什么?有什么选择?
TLDR;
- 我能否在不手动重新渲染的情况下获得 DOM 元素的最新位置?
- 如何在渲染期间手动重新渲染而不更改 ref 值(因为它不安全 - 在 React 严格模式下它将被调用两次,通常只调用一次)?
【问题讨论】:
标签: javascript reactjs getboundingclientrect