useEffect 钩子中的回调何时触发?

When callback in useEffect hook fires?

我读到,useEffect 是异步的,所以 React 首先渲染和加载页面,只有在 useEffect 中的回调触发后,所以有时我们看到某些组件中的值变化非常快。

但是当我尝试在 useEffect 回调中使用自己创建的“sleep-for-3-seconds”时,React 会等到 3 秒过去,然后才渲染和加载页面,所以我看到空白页面 3秒。我们也可以在 3 秒延迟后注意到快速变化的值。我不明白为什么会这样,因为 useEffect 回调应该在渲染和加载页面后 运行。

能否解释一下为什么会这样?

代码沙箱: Link

代码:

import { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [state, setState] = useState(1200000000);
  useEffect(() => {
    let start = new Date().getTime();
    let end = start;
    while (end < start + 3000) {
      end = new Date().getTime();
    }
    setState(3);
  }, []);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <div>Value is : {state}</div>
    </div>
  );
}

I've read, that useEffect is asynchronous, so React firstly renders and mounts page and only after callback in useEffect fires, so sometimes we see value changing really fast in some components.

正确。记录在案 here:

The function passed to useEffect will run after the render is committed to the screen.

here

Timing of effects

Unlike componentDidMount and componentDidUpdate, the function passed to useEffect fires after layout and paint, during a deferred event.

(他们强调)

继续你的问题:

But when I tried to use self-created "sleep-for-3-seconds" in useEffect callback, React waits until 3 seconds pass, and only after that renders and mounts page, so I see blank page for 3 seconds.

真奇怪,我没有看到这种行为:

const { useState, useEffect} = React;

function App() {
    const [state, setState] = useState(1200000000);
    useEffect(() => {
        let start = new Date().getTime();
        let end = start;
        while (end < start + 3000) {
            end = new Date().getTime();
        }
        setState(3);
    }, []);
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <div>Value is : {state}</div>
      </div>
    );
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

当我 运行 时,我看到页面呈现初始 1200000000 状态值,然后三秒后我看到它更新为修改后的状态值。

我也没有看到这个更简单的例子:

const {useEffect} = React;

const Example = () => {
    useEffect(() => {
        const stop = Date.now() + 3000;
        while (Date.now() < stop) {
            // Wait (NEVER DO THIS IN REAL CODE)
        }
        console.log("Done waiting");
    }, []);
    return <div>x</div>;
};

ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

当我 运行 时,我看到组件呈现的 x,然后三秒后我看到等待完成的 console.log

但是如果您没有看到您描述的内容,那只是因为浏览器在 [=14= 之前还没有机会真正绘制页面] 被调用,尽管 React 的代码试图确保是这种情况。由于您的代码是 busy-wait,主线程与该循环捆绑在一起,浏览器无法更新页面显示。

但是,我再一次在 useEffect 中看不到这一点(你会在 useLayoutEffect 中看到)。

你看到的黑屏,根据代码是正确的。

因为您使用的代码代码片段是 busy-wait 脚本并使您的其他 javascript 代码停止并 UI 冻结。

这在用户体验和性能方面都不好。

    let start = new Date().getTime();
    let end = start;
    while (end < start + 3000) {
      end = new Date().getTime();
    }

所以你可以这样做:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

useEffect(() => {
    delay(3000).then(res => {
      setState(3);
    });
  }, []);