useState 在 React 生命周期中的位置

bam*_*ami 3 reactjs react-hooks react-lifecycle-hooks

我真的很想了解 React 功能组件的生命周期。在许多网站中,您会看到这三个步骤:
1-安装2-渲染3-卸载。

但是,在 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]);
};

Run Code Online (Sandbox Code Playgroud)

我想知道 :

  1. 当 countDown 状态和 x 变量在 useEffect 之前或之后(或内部)声明时?

  2. iffor 短语被声明时(在此示例if短语中),它们确实在 useEffect 内吗?

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

T.J*_*der 6

生命周期

\n
\n

1-安装 2-渲染 4-卸载。

\n
\n

它更像是(文档):

\n
    \n
  • 挂载\n
      \n
    • 渲染 (React 调用您的组件函数)
    • \n
    • 提交到 DOM (React 使用 DOM 协调元素)
    • \n
    • 布局效果 (React调用通过 安排的相关布局效果回调useLayoutEffect
    • \n
    • 效果 (React 调用通过安排的相关非布局效果回调useEffect))
    • \n
    \n
  • \n
  • 更新(重复)\n
      \n
    • 渲染 (React 调用您的组件函数)
    • \n
    • 提交到 DOM (React 使用 DOM 协调元素)
    • \n
    • 布局效果 (React调用通过 安排的相关布局效果回调useLayoutEffect
    • \n
    • 效果 (React 调用通过安排的相关非布局效果回调useEffect))
    • \n
    • 清理(​​如果有)(React React 调用任何效果清理回调)
    • \n
    \n
  • \n
  • 卸载\n
      \n
    • 删除 (React 从 DOM 中删除元素)
    • \n
    • 清理(​​如果有)(React React 调用任何效果清理回调)
    • \n
    \n
  • \n
\n

你的问题

\n
\n

当 countDown 状态和 x 变量在 useEffect 之前或之后(或内部)声明时?

\n
\n

前。React 库无法更改 JavaScript 代码执行的方式。调用useState和关联的声明位于useEffect调用之前,因此它们发生在调用之前。

\n
\n

当声明 if 或 for 短语时(在本例中为 if 短语),它们确实在 useEffect 内吗?

\n
\n

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

\n

你的代码如何运行

\n

这里的循环是:

\n
    \n
  1. 首次渲染\n
      \n
    1. React 为元素创建幕后实例存储。
    2. \n
    3. React 调用您的组件函数。\n
        \n
      1. 您的代码声明了一个局部x变量并将其设置为0.
      2. \n
      3. 您的代码声明countDownandsetCountDown并调用useState,它在实例存储中分配一个状态槽;你的代码存储了什么useState返回的内容(初始状态值和设置器)存储在这些常量中。
      4. \n
      5. 您的x = x + 1语句运行,更新x1.
      6. \n
      7. 你的if语句运行,但条件永远不会为真\xc2\xa0\xe2\x80\x94x是一个局部变量,而不是一个状态成员,所以它的值永远是1这一点上。
      8. \n
      9. 您的调用useEffect安排了效果回调countDown
      10. \n
      11. 此时,您的代码应该返回以下元素Countdown
      12. \n
      \n
    4. \n
    \n
  2. \n
  3. 第一次提交/“挂载”\n
      \n
    1. React 获取应该返回的元素Countdown提交到 DOM(使 DOM 显示它们所描述的内容)。
    2. \n
    \n
  4. \n
  5. React 调用你的useEffect回调(useEffect回调始终在挂载后立即调用)\n
      \n
    1. 您的回调创建一个间隔计时器,运行时将调用setCountDown.
    2. \n
    3. 您的回调日志x,这将是1.
    4. \n
    \n
  6. \n
  7. 定时器调用setCountDown,改变值。
  8. \n
  9. 第二次渲染\n
      \n
    1. React 调用您的函数来重新渲染。\n
        \n
      1. 您的代码声明一个新的局部x变量并将其设置为0.
      2. \n
      3. 您的代码声明countDownandsetCountDown并调用useState,它从实例存储中检索更新的状态;你的代码存储了什么useState返回的内容(当前状态值和设置器)存储在这些常量中。
      4. \n
      5. 您的x = x + 1语句运行,更新x1.
      6. \n
      7. 你的if语句可以运行,但条件永远不会为真。
      8. \n
      9. 您的调用会useEffect在更改时安排效果回调countDown
      10. \n
      11. 此时,您的代码应该从 中返回元素Countdown
      12. \n
      \n
    2. \n
    \n
  10. \n
  11. 因为countDown发生了变化,React 会调用您的useEffect回调\n
      \n
    1. 您的回调创建一个新的间隔计时器,运行时将调用setCountDown.
    2. \n
    3. 您的回调日志x将是1.
    4. \n
    \n
  12. \n
  13. 依此类推,直到/除非该组件被其父组件卸载。
  14. \n
\n

代码问题

\n

您显示的代码中有几个错误

\n
    \n
  1. 您永远不会取消间隔计时器,但每次更改时都会创建一个新的计时器countDown。这将很快导致数百甚至数千个计时器全部触发更新调用。您应该:\n
      \n
    1. 至少,记住计时器句柄并在效果清理中取消计时器。
    2. \n
    3. 考虑不用作countDown依赖项,因此效果仅在安装时运行。然后使用 的回调形式setCountDown
    4. \n
    \n
  2. \n
  3. (如前所述)您的组件永远不会返回任何元素
  4. \n
  5. 您的代码似乎期望在函数调用之间保留 的值x,但它是局部变量,因此每次都会重新创建。
  6. \n
  7. countDown当到达 时,没有什么特别的事情发生0,所以它会继续前进-1-2等等。
  8. \n
\n

更新后的版本

\n

这是带有一些注释的更新版本。我本来打算删除它,x因为它并没有真正用于任何用途,但后来认为最好留下评论。我没有对上面的#4 做任何事情,因为我不确定你想做什么。

\n
const Countdown = () => {\n    let x = 0;\n    const [countDown, setCountDown] = useState(10);\n\n    x = x + 1;\n    if (x > 100) {  // `x` will always be `1` here, remember that\n        x = 0;      // `x` is a *local variable*\n    }\n\n    useEffect(() => {\n        const interval = setInterval(() => {\n            // Use the callback form of the setter so you can update the\n            // up-to-date value\n            setCountDown((c) => c - 1);\n            // Will always show 1\n            console.log(x);\n        }, 1000);\n        // Return a cleanup callback that removes the interval timer\n        return () => {\n            clearInterval(interval);\n        };\n    }, []);\n    // ^^ don\'t use `countDown` as a dependency (in this particular case),\n    // since we don\'t use it (anymore, now we use the callback setter)\n\n    // Return some elements\n    return <div>{countDown}</div>;\n};\n
Run Code Online (Sandbox Code Playgroud)\n

\r\n
\r\n
const Countdown = () => {\n    let x = 0;\n    const [countDown, setCountDown] = useState(10);\n\n    x = x + 1;\n    if (x > 100) {  // `x` will always be `1` here, remember that\n        x = 0;      // `x` is a *local variable*\n    }\n\n    useEffect(() => {\n        const interval = setInterval(() => {\n            // Use the callback form of the setter so you can update the\n            // up-to-date value\n            setCountDown((c) => c - 1);\n            // Will always show 1\n            console.log(x);\n        }, 1000);\n        // Return a cleanup callback that removes the interval timer\n        return () => {\n            clearInterval(interval);\n        };\n    }, []);\n    // ^^ don\'t use `countDown` as a dependency (in this particular case),\n    // since we don\'t use it (anymore, now we use the callback setter)\n\n    // Return some elements\n    return <div>{countDown}</div>;\n};\n
Run Code Online (Sandbox Code Playgroud)\r\n
const { useState, useEffect } = React;\n\nconst Countdown = () => {\n    let x = 0;\n    const [countDown, setCountDown] = useState(10);\n\n    x = x + 1;\n    if (x > 100) {  // `x` will always be `1` here, remember that\n        x = 0;      // `x` is a *local variable*\n    }\n\n    useEffect(() => {\n        const interval = setInterval(() => {\n            // Use the callback form of the setter so you can update the\n            // up-to-date value\n            setCountDown((c) => c - 1);\n            // Will always show 1\n            console.log(x);\n        }, 1000);\n        // Return a cleanup callback that removes the interval timer\n        return () => {\n            clearInterval(interval);\n        };\n    }, []);\n    // ^^ don\'t use `countDown` as a dependency (in this particular case),\n    // since we don\'t use it (anymore, now we use the callback setter)\n\n    // Return some elements\n    return <div>{countDown}</div>;\n};\n\nconst Example = () => {\n    return <Countdown />;\n};\n\nconst root = ReactDOM.createRoot(document.getElementById("root"));\nroot.render(<Example />);
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

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

\n

\r\n
\r\n
<div id="root"></div>\n\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Run Code Online (Sandbox Code Playgroud)\r\n
const { useState, useEffect } = React;\n\nconst Countdown = () => {\n    const [countDown, setCountDown] = useState(10);\n\n    useEffect(() => {\n        const interval = setInterval(() => {\n            // We could cancel the interval from within the setter\n            // callback. It\'s a bit dodgy, though, to have side-\n            // effects in setter callbacks.\n            setCountDown((c) => {\n                const updated = c - 1;\n                if (updated === 0) {\n                    clearInterval(interval);\n                }\n                return updated;\n            });\n        }, 1000);\n        return () => {\n            clearInterval(interval);\n        };\n    }, []);\n\n    return <div>{countDown === 0 ? "Done" : countDown}</div>;\n};\n\nconst Example = () => {\n    return <Countdown />;\n};\n\nconst root = ReactDOM.createRoot(document.getElementById("root"));\nroot.render(<Example />);
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

\r\n
\r\n
<div id="root"></div>\n\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Run Code Online (Sandbox Code Playgroud)\r\n
const { useState, useEffect, useRef } = React;\n\nconst Countdown = () => {\n    // We could store the timer handle in a ref (which is maintained\n    // across renders) and use a second `useEffect` to cancel it when\n    // `countDown` reaches zero.\n    const intervalRef = useRef(0);\n    const [countDown, setCountDown] = useState(10);\n\n    useEffect(() => {\n        intervalRef.current = setInterval(() => {\n            setCountDown((c) => c - 1);\n        }, 1000);\n        return () => {\n            // (It\'s okay if this tries to clear an interval that\n            // isn\'t running anymore.)\n            clearInterval(intervalRef.current);\n        };\n    }, []);\n\n    useEffect(() => {\n        if (countDown === 0) {\n            clearInterval(intervalRef.current);\n        }\n    }, [countDown]);\n\n    return <div>{countDown === 0 ? "Done" : countDown}</div>;\n};\n\nconst Example = () => {\n    return <Countdown />;\n};\n\nconst root = ReactDOM.createRoot(document.getElementById("root"));\nroot.render(<Example />);
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n