React如何实现useEffect和useLayoutEffect?

ste*_*bot 7 event-handling reactjs react-hooks

useEffect我理解和之间的区别,useLayoutEffect但我很好奇它是如何实现的。

我创建了这个沙箱,它显示了事情发生的顺序:

Log.js

export default function Log(count, ref) {
  console.log(`Synchronous ${count}: Ran after ${ref.current}`);
  queueMicrotask(() =>
    console.log(`Microtask ${count}: Ran after ${ref.current}`)
  );
  setImmediate(() => console.log(`Task ${count}: Ran after ${ref.current}`));
}
Run Code Online (Sandbox Code Playgroud)

App.scala

import { forwardRef, useEffect, useLayoutEffect, useState } from "react";

import Log from "./Log";

const App = forwardRef((props, ref) => {
  const [, setCount] = useState(0);
  ref.current = "render";
  Log(2, ref);
  useLayoutEffect(() => {
    ref.current = "useLayoutEffect";
  });
  Log(3, ref);
  useEffect(() => {
    ref.current = "useEffect";
  });
  Log(4, ref);
  return (
    <button
      onClick={() => {
        console.log("Click");
        setCount((x) => x + 1);
      }}
    >
      Click
    </button>
  );
});

export default App;
Run Code Online (Sandbox Code Playgroud)

index.js

import React, { StrictMode } from "react";
import ReactDOM from "react-dom";

import App from "./App";
import Log from "./Log";

const rootElement = document.getElementById("root");

const ref = React.createRef();
ref.current = "start";

Log(1, ref);

ReactDOM.render(
  <StrictMode>
    <App ref={ref} />
  </StrictMode>,
  rootElement
);

Log(5, ref);
Run Code Online (Sandbox Code Playgroud)

运行应用程序并单击按钮后,控制台显示:

Synchronous 1: Ran after start 
Synchronous 2: Ran after render 
Synchronous 3: Ran after render 
Synchronous 4: Ran after render 
Synchronous 5: Ran after useLayoutEffect 
Microtask 1: Ran after useLayoutEffect 
Microtask 2: Ran after useLayoutEffect 
Microtask 3: Ran after useLayoutEffect 
Microtask 4: Ran after useLayoutEffect 
Microtask 2: Ran after useLayoutEffect 
Microtask 3: Ran after useLayoutEffect 
Microtask 4: Ran after useLayoutEffect 
Microtask 5: Ran after useLayoutEffect 
Task 1: Ran after useLayoutEffect 
Task 2: Ran after useLayoutEffect 
Task 3: Ran after useLayoutEffect 
Task 4: Ran after useLayoutEffect 
Task 2: Ran after useLayoutEffect 
Task 3: Ran after useLayoutEffect 
Task 4: Ran after useLayoutEffect 
Task 5: Ran after useEffect 
Click 
Synchronous 2: Ran after render 
Synchronous 3: Ran after render 
Synchronous 4: Ran after render 
Microtask 2: Ran after useLayoutEffect 
Microtask 3: Ran after useLayoutEffect 
Microtask 4: Ran after useLayoutEffect 
Microtask 2: Ran after useLayoutEffect 
Microtask 3: Ran after useLayoutEffect 
Microtask 4: Ran after useLayoutEffect 
Task 2: Ran after useEffect 
Task 3: Ran after useEffect 
Task 4: Ran after useEffect 
Task 2: Ran after useEffect 
Task 3: Ran after useEffect 
Task 4: Ran after useEffect
Run Code Online (Sandbox Code Playgroud)

我注意到的第一件事是:

Synchronous 5: Ran after useLayoutEffect 
Run Code Online (Sandbox Code Playgroud)

这表明它useLayoutEffect是在之前安排的微任务之前触发的。这意味着它不是作为微任务实现的,而必须是一些内部任务队列(这并不奇怪)。

接下来的事情是:

Task 4: Ran after useLayoutEffect 
Run Code Online (Sandbox Code Playgroud)

由于useEffect让浏览器渲染并运行其他任务,我希望将其实现为一个任务,因此在下一个事件循环迭代中安排在任务 3 和 4 之间。

关于为什么它发生在任务 4 和 5 之间,我能想到的唯一解释是,React 正在创建一个任务,但不是在调用钩子时调度它,而是在内部对其进行排队,然后在渲染完成后创建一个任务执行该效果队列。

单击按钮后的下一个渲染会显示不同的内容:

Task 2: Ran after useEffect 
Task 3: Ran after useEffect 
Task 4: Ran after useEffect 
Run Code Online (Sandbox Code Playgroud)

在这里,它似乎在实际调用钩子之前预先安排了任务来处理效果。

任何人都可以阐明它的实际工作原理吗?


奖金问题

严格模式导致组件每次渲染两次,这就是为什么我们看到以下重复条目:

Microtask 2: Ran after useLayoutEffect 
Microtask 3: Ran after useLayoutEffect 
Microtask 4: Ran after useLayoutEffect 
Run Code Online (Sandbox Code Playgroud)

为什么这些没有重复的条目?

Synchronous 2: Ran after render 
Synchronous 3: Ran after render 
Synchronous 4: Ran after render 
Run Code Online (Sandbox Code Playgroud)