繁体   English   中英

useState 在生命周期中的位置

[英]useState place in lifecycle in react

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

但是,在 useeffect() function 之前编写的其他代码又如何假设:


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. 当 countDown state 和 x 变量在 useEffect 之前或之后(或在其内部)声明时?

  2. 当声明iffor phrase 时(在此示例中为if phrase),它们确实在 useEffect 中吗?

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

生命周期

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

它更像是(文档):

    • 渲染(React 调用你的组件函数)
    • 提交给 DOM (React 将元素与 DOM 协调)
    • 布局效果(React 调用通过useLayoutEffect调度的相关布局效果回调)
    • 效果(React 调用通过useEffect安排的相关非布局效果回调))
  • 更新(重复)
    • 渲染(React 调用你的组件函数)
    • 提交给 DOM (React 将元素与 DOM 协调)
    • 布局效果(React 调用通过useLayoutEffect调度的相关布局效果回调)
    • 效果(React 调用通过useEffect安排的相关非布局效果回调))
    • Cleanup if any (React React 调用任何效果清理回调)
  • 卸载
    • 移除(React 从 DOM 中移除元素)
    • Cleanup if any (React React 调用任何效果清理回调)

你的问题

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

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

当声明 if 或 for phrase 时(在此示例中为 if phrase),它们确实在 useEffect 中吗?

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

您的代码如何运行

这里的循环是:

  1. 首先渲染
    1. React 为元素创建幕后实例存储。
    2. React 调用您的组件 function。
      1. 您的代码声明了一个本地x变量并将其设置为0
      2. 您的代码声明countDownsetCountDown并调用useState ,它在实例存储中分配一个 state 槽; 您的代码将useState返回的内容(初始 state 值和设置器)存储在这些常量中。
      3. 您的x = x + 1语句运行,将x更新为1
      4. 您的if语句运行,但条件永远不会为真 — x是局部变量,而不是 state 成员,因此此时它的值将始终为1
      5. 您对useEffect的调用会在countDown更改时安排效果回调。
      6. 此时,您的代码应该从Countdown返回元素。
  2. 第一次提交/“挂载”
    1. React 获取Countdown应返回的元素并将它们提交给 DOM(使 DOM 显示它们所描述的内容)。
  3. React 调用您的useEffect回调( useEffect回调总是在挂载之后调用)
    1. 您的回调创建了一个间隔计时器,该计时器在运行时将调用setCountDown
    2. 您的回调记录x ,它将是1
  4. 计时器调用setCountDown来更改值。
  5. 二次渲染
    1. React 调用您的 function 重新渲染。
      1. 您的代码声明了一个新的本地x变量并将其设置为0
      2. 您的代码声明countDownsetCountDown并调用useState ,它从实例存储中检索更新后的 state; 您的代码将useState返回的内容(当前 state 值和设置器)存储在这些常量中。
      3. 您的x = x + 1语句运行,将x更新为1
      4. 您的if语句运行,但条件永远不会为真。
      5. 您对useEffect的调用会在countDown更改时安排效果回调。
      6. 此时,您的代码应该从Countdown返回元素。
  6. 因为countDown改变了,React 调用你的useEffect回调
    1. 您的回调会创建一个新的间隔计时器,该计时器在运行时将调用setCountDown
    2. 您的回调记录x ,它将是1
  7. 依此类推,直到/除非该组件被其父组件卸载。

代码问题

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

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

更新后的版本

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

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>

useEffec 在生命周期中的位置

在组件代码中放置useEffect的位置并不重要,所有效果将始终在每次渲染运行。 useEffect在组件代码中的唯一位置可能是重要的是如果您有多个useEffect s,因为它们将按照您编写它们的顺序执行。

x 变量/状态

至于你的x变量,这不是在反应中起作用的东西,因为该变量将在每次渲染时重新声明。 跟踪 state 的方法是使用useState 我认为您可以从同一个 state 跟踪这两个值,但是如果您想要额外的显式 state,则需要使用另一个useState挂钩。 一般约定是在组件的顶部声明 state 钩子(但它们不是必须的)。

如果语句

React hooks 的规则之一是所有 hooks 必须在每次渲染时运行。 您仍然可以在组件和自定义挂钩中使用 id 语句,但它们必须在组件中的所有挂钩之后运行,或者您可以挂钩中声明 then - 这样挂钩会在每次渲染时执行,但其中的代码块将仅在 if 语句的条件为真时运行。

useEffect(() => {
  if (condition) {
    code to run...
  }
}, [countDown]);

补充笔记

关于useEffect需要记住的另一件重要事情是,有时它们需要用 return 语句清除。 在您的情况下,您声明了一个间隔,而没有返回clearInterval ,您的useEffect将在每个渲染上创建一个额外的间隔,这将导致您的计数器不同步

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM