I really want to understand the lifecycle of react functional component. In many websites you see these three steps:
1-mounthing 2-render 3-unmounthing.

但是,在useEffect()函数之前编写的其他代码怎么办?例如,假定如下:


const Countdown = () => {
  let x = 0;
  const [countDown, setCountDown] = useState(10)
   
   x = x + 1
   
   if(x > 100) { 
     x = 0
   }

  useEffect(() => {
    const interval = setInterval(() => {
        setCountDown(countDown - 1);
        console.log(x)
      }, 1000);
    }, [countDown]);
};

我想知道:

  1. 当在Use Effect之前或之后(或在它内部)声明倒计时状态和x变量时?

  2. 当声明了iffor个短语(在本例中为if个短语)时,它们真的在Use Effect中吗?

什么是加载页面顺序?执行的起点是什么?

推荐答案

生命周期

1-装载2-渲染4-卸载.

它更像(docs):

  • Mount
    • Render,(React calls your component function).
    • Commit到DOM (React reconciles elements with the DOM)
    • #
    • #
  • Update (repeats)
    • Render,(React calls your component function).
    • Commit到DOM (React reconciles elements with the DOM)
    • #
    • #
    • Cleanup(如果有的话)(React React calls any effect cleanup callbacks)
  • Unmount
    • Removal,(React removes elements from the DOM).
    • Cleanup(如果有的话)(React React calls any effect cleanup callbacks)

您的问题

当在Use Effect之前或之后(或在它内部)声明倒计时状态和x变量时?

在此之前.Reaction库不能改变执行JavaScript代码的方式.useState调用和相关声明在useEffect调用之前,因此它们发生在useEffect调用之前.

当If或For短语被声明时(在本例中为If短语),它们真的在Use Effect中吗?

不会,只有useEffect回调中的代码才会被调用为效果.

代码的运行方式

这里的循环是:

  1. 第一次渲染
    1. Reaction为元素创建幕后实例存储.
    2. Reaction调用您的组件函数.
      1. 您的代码声明了一个本地x变量,并将其设置为0.
      2. 您的代码声明countDownsetCountDown并调用useState,这将在实例存储中分配一个状态槽;您的代码将useState返回的内容(初始状态值和setter)存储在这些常量中.
      3. 您的x = x + 1语句运行,将x更新为1.
      4. 您的if语句运行,但条件永远不为真,因为-x是局部变量,而不是状态成员,因此它的值在此时始终为1.
      5. 您对useEffect的调用计划在countDown更改时进行效果回调.
      6. 此时,您的代码应该从Countdown返回元素.
  2. 第一次提交/"装载"
    1. Reaction接受Countdown应该返回的元素,并将它们commits返回到DOM(使DOM显示它们所描述的内容).
  3. Reaction调用您的useEffect个回调(总是在挂载之后调用useEffect个回调)
    1. 您的回调创建一个时间间隔计时器,该计时器在运行时将调用setCountDown.
    2. 您的回调日志(log)为x,将为1.
  4. 定时器调用setCountDown,更改该值.
  5. Second render
    1. React calls your function to re-render.
      1. 您的代码声明了一个新的局部x变量,并将其设置为0.
      2. 您的代码声明countDownsetCountDown并调用useState,后者从实例存储中检索更新后的状态;您的代码将useState返回的内容(当前状态值和setter)存储在这些常量中.
      3. 运行您的x = x + 1语句,将x更新为1.
      4. 您的if语句将运行,但条件永远不会为真.
      5. 您对useEffect的呼叫计划在countDown更改时进行效果回调.
      6. 此时,您的代码应该返回Countdown中的元素.
  6. Because countDown changed, React calls your useEffect callback
    1. 您的回调创建了一个新的时间间隔计时器,运行时将调用setCountDown.
    2. 您的回调记录为x,也就是1.
  7. 依此类推,直到/除非组件被其父组件卸载.

代码的问题

您所展示的代码中有几个错误

  1. 您永远不会取消间隔计时器,但每次更改countDown次时,您都会创建一个新的计时器.这将很快导致数百乃至数千个计时器全部触发更新调用.您应该:
    1. 至少要记住计时器句柄并在效果清理中取消计时器.
    2. 考虑不使用countDown作为依赖,这样效果只在挂载时生效.然后使用回调表单setCountDown.
  2. (如前所述)您的组件永远不会返回任何元素
  3. 您的代码似乎希望在调用函数之间保持值x,但它是一个局部变量,所以每次都会重新创建它.
  4. countDown达到0时,没有什么特别的事情发生,所以它只会一直走到-1-2,等等.

更新版

这是一个带有一些注释的更新版本.我本来打算删除x,因为它并不是真的用来做任何事情,但后来我想,最好还是留下 comments .我没有对上面的第四点做任何事情,因为我不确定你想要做什么.

const Countdown = () => {
    let x = 0;
    const [countDown, setCountDown] = useState(10);

    x = x + 1;
    if (x > 100) {  // `x` will always be `1` here, remember that
        x = 0;      // `x` is a *local variable*
    }

    useEffect(() => {
        const interval = setInterval(() => {
            // Use the callback form of the setter so you can update the
            // up-to-date value
            setCountDown((c) => c - 1);
            // Will always show 1
            console.log(x);
        }, 1000);
        // Return a cleanup callback that removes the interval timer
        return () => {
            clearInterval(interval);
        };
    }, []);
    // ^^ don't use `countDown` as a dependency (in this particular case),
    // since we don't use it (anymore, now we use the callback setter)

    // Return some elements
    return <div>{countDown}</div>;
};

const { useState, useEffect } = React;

const Countdown = () => {
    let x = 0;
    const [countDown, setCountDown] = useState(10);

    x = x + 1;
    if (x > 100) {  // `x` will always be `1` here, remember that
        x = 0;      // `x` is a *local variable*
    }

    useEffect(() => {
        const interval = setInterval(() => {
            // Use the callback form of the setter so you can update the
            // up-to-date value
            setCountDown((c) => c - 1);
            // Will always show 1
            console.log(x);
        }, 1000);
        // Return a cleanup callback that removes the interval timer
        return () => {
            clearInterval(interval);
        };
    }, []);
    // ^^ don't use `countDown` as a dependency (in this particular case),
    // since we don't use it (anymore, now we use the callback setter)

    // Return some elements
    return <div>{countDown}</div>;
};

const Example = () => {
    return <Countdown />;
};

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

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

如果我们希望倒计时在达到0时停止并关闭计时器,有几种方法可以做到这一点,参见显示不同方法的两个现场示例中的注释:

const { useState, useEffect } = React;

const Countdown = () => {
    const [countDown, setCountDown] = useState(10);

    useEffect(() => {
        const interval = setInterval(() => {
            // We could cancel the interval from within the setter
            // callback. It's a bit dodgy, though, to have side-
            // effects in setter callbacks.
            setCountDown((c) => {
                const updated = c - 1;
                if (updated === 0) {
                    clearInterval(interval);
                }
                return updated;
            });
        }, 1000);
        return () => {
            clearInterval(interval);
        };
    }, []);

    return <div>{countDown === 0 ? "Done" : countDown}</div>;
};

const Example = () => {
    return <Countdown />;
};

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

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

const { useState, useEffect, useRef } = React;

const Countdown = () => {
    // We could store the timer handle in a ref (which is maintained
    // across renders) and use a second `useEffect` to cancel it when
    // `countDown` reaches zero.
    const intervalRef = useRef(0);
    const [countDown, setCountDown] = useState(10);

    useEffect(() => {
        intervalRef.current = setInterval(() => {
            setCountDown((c) => c - 1);
        }, 1000);
        return () => {
            // (It's okay if this tries to clear an interval that
            // isn't running anymore.)
            clearInterval(intervalRef.current);
        };
    }, []);

    useEffect(() => {
        if (countDown === 0) {
            clearInterval(intervalRef.current);
        }
    }, [countDown]);

    return <div>{countDown === 0 ? "Done" : countDown}</div>;
};

const Example = () => {
    return <Countdown />;
};

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

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

Reactjs相关问答推荐

如何在useEffect中使用useParams()

在迁移到Redux—Toolkit 2.0和Redux5.0之后,我在extraReducer和Slice方面有点挣扎,

单击空白区域时,Reaction Multiple下拉组件不关闭

状态改变不会触发重新渲染

MERN,将动态列表中的元素插入数组

使用获取的数据更新状态,但在try console.log 时它给出未定义

如何更新 FunctionComponentElement 的 props

Mui DataGrid 在第二页和前一页上显示项目时出现问题

useEffect 内的 onClick 和 addEventListener 之间的 click 事件顺序差异

从 redux 调用UPDATE_DATA操作时状态变得未定义

当`useSelector`回调中的属性值更新时,将会使用更新后的值吗?

React - 如何重定向页面以显示数据修改?

未处理的拒绝 (TypeError):无法读取未定义的属性(读取协议)

表单错误后 Ionic React 无法 Select 单选按钮

来自一个自定义 React Native 组件的样式从另一个自定义组件移除样式

如何在 nextjs 中单击按钮时动态导入和渲染组件?

如何使用 React Hook Form 将寄存器作为 props 发送到子组件来收集数据?

React Final Form - 单选按钮在 FieldArray 中不起作用

为什么我在运行一些 npm react 命令时会收到这些警告?

在 Redux 中更新布尔状态时出错