在浏览器有机会绘制之前,React 如何正确测量 useLayoutEffect 挂钩中的 DOM 元素?

shi*_*lok 9 reactjs

在 React 官方文档中,useLayoutEffect提到了:

签名与 相同useEffect,但它在所有 DOM 突变后同步触发。使用它从 DOM 读取布局并同步重新渲染。useLayoutEffect在浏览器有机会绘制之前,内部计划的更新将同步刷新。

此外,useLayoutEffect我们可以在浏览器实际重新绘制之前读取更新的尺寸。

React 是如何做到这一点的?

Ali*_*ssa 5

so @Marko is right.

the browser rendering cycle is split into phases, the relevant ones are:

  1. style - merge HTML with CSS
  2. layout - calculate all elements sizes and positions
  3. paint

Mozilla docs

useLayoutEffect is run after layout but before paint

to achieve that all that you need to do (or what React does) is run the useLayoutEffect functions synchronously after adding or changing elements in the DOM tree.

The browser can't run the painting phase unless you release the thread.

发生的情况是,如果您向浏览器询问元素的大小,它会同步运行布局阶段来给您答案。

这是一个片段和一个沙箱,展示了这个想法:

const conatiner = document.getElementById("container");

function busyWait() {
  const array = new Array(100000).fill(Math.random());
  let sum = 0;
  array.forEach((val, index) => {
    const item = array.find((val, i) => i === index);
    sum = sum + item;
  });
  return sum;
}

function addElement() {
  const newElement = document.createElement("p");
  newElement.innerText =
    "this is a paragraph, browser need to measure it to know its size";

  if (conatiner) {
    conatiner.appendChild(newElement);
    console.log(
      "layout phase element size",
      JSON.stringify(newElement.getBoundingClientRect())
    );
    const sum = busyWait();
    console.log(sum);

    setTimeout(() => {
      console.log(
        "after paint element size",
        JSON.stringify(newElement.getBoundingClientRect())
      );
    }, 10);
  }
}

setTimeout(addElement, 100);
Run Code Online (Sandbox Code Playgroud)
<div>
      we are going to test layout / replaint stages
      <div id="container" style="width: 200px;"></div>
</div>
Run Code Online (Sandbox Code Playgroud)

  • 我添加一个新元素
  • 询问其尺寸
  • 记录它
  • 然后忙等待(不将线程释放给浏览器)=>您可以在日志中看到正确的大小,但在屏幕上看不到该元素


ajo*_*obi 0

如果我没记错的话,它的工作原理是这样的:

  1. 您单击更新计数器的按钮(例如)。
  2. React 更新计数器状态。
  3. React 更新 DOM(浏览器尚未渲染它!)。
  4. 浏览器呈现更改。

回调useLayoutEffect将在步骤 3 和 4 之间触发。