为什么工具提示示例中使用 useEffect render-blocking(paint-blocking) ?

use*_*782 5 javascript browser reactjs react-hooks

我正在关注新的 React 文档中的钩子工具提示示例。useLayoutEffect根据他们的解释,我假设以下是事情的执行顺序:

React render() --> 协调 --> 如果 virtual dom 改变则更新 DOM --> DOM 更新完成 --> useLayoutEffect 被执行 --> broswer 绘制并触发某种 LayoutPaint 事件 --> useEffect 被触发

为了验证这一点,我将文档示例的 tooltip.js 文件中的useLayouteffecthook 与hook 交换了。useEffect

这是修改后的例子

我放置了一些for循环来减慢useEffect钩子的执行速度。现在,当您加载修改后的示例并将鼠标移到三个按钮中的任何一个时,您将看到第一次使用工具提示在错误的位置进行绘制,然后 useEffect 需要 1-2 秒,然后您将看到工具提示在正确的位置进行另一次重新绘制。到目前为止一切都很好,但现在任何以后的鼠标悬停在同一个按钮上,您都会看到错误位置绘制等待 useEffect 完成,然后正确位置绘制在几毫秒内发生。

在此输入图像描述

所以我有两个问题:

  1. 为什么后来的鼠标悬停会导致useEffect渲染阻塞?

  2. React 如何确保useLayoutEffect阻止浏览器绘制发生,如果useLayoutEffect提到其中的任何状态更新,则触发另一个渲染->重绘,同时完全不允许发生先前的浏览器绘制。在我们的例子中,-60px处的工具提示根本没有被绘制。

Rab*_*bit 0

  1. useEffect钩子通常不会阻塞渲染。但是,如果钩子内发生大量计算,则可能会导致显着的延迟,并且看起来好像阻塞了渲染。在您的情况下, 中的嵌套循环useEffect可能会导致此行为。

  2. useLayoutEffect在 DOM 突变之后、浏览器绘制之前同步运行。如果状态更新发生在 内useLayoutEffect,React 会在useLayoutEffect完成后立即刷新此状态更新,从而在浏览器有机会绘制之前导致同步重新渲染。这可以防止绘制中间状态(例如 -60px 处的工具提示)。

import { useRef, useEffect, useState } from "react";
import { createPortal } from "react-dom";
import TooltipContainer from "./TooltipContainer.js";

export default function Tooltip({ children, targetRect }) {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(60);

  useEffect(() => {
    if (ref.current) {
      const { height } = ref.current.getBoundingClientRect();
      setTooltipHeight(height);
    }
  }, [targetRect]); // Re-run this effect when targetRect changes

  let tooltipX = 0;
  let tooltipY = 0;
  if (targetRect !== null) {
    tooltipX = targetRect.left;
    tooltipY = targetRect.top - tooltipHeight;
    if (tooltipY < 0) {
      // It doesn't fit above, so place below.
      tooltipY = targetRect.bottom;
    }
  }

  return createPortal(
    <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}>
      {children}
    </TooltipContainer>,
    document.body
  );
}
Run Code Online (Sandbox Code Playgroud)