【问题标题】:React Hooks useEffect saves the last state and won't update it with the new one. It runs twice with two different statesReact Hooks useEffect 保存最后一个状态,不会用新状态更新它。它以两种不同的状态运行两次
【发布时间】:2021-06-27 16:28:27
【问题描述】:

我正在实现一个简单的计时器,并在每次计数减 1 时使用 use effect 运行一个函数。

我有一个按钮,按下时会将计数状态更改为 10(因此,如果当前计数为 6,则按下此按钮应返回 10 并再次开始减少)。

计时器工作正常,但是当我按下按钮时,它会创建另一种状态,但不会更新原始状态。

例子:

从 10 点开始计数

10

9

8

7

我按下按钮回到 10

6     10

5     9

4     8 

这就是正在发生的事情。我不知道为什么 useEffect 会卡在旧状态。

这是我的代码:

import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
import Button from "react-bootstrap/Button";

export default function PagTimer() {
  let history = useHistory();
  const [contador, setContador] = useState(10);

  const AddTime = () => {
    setContador(10);
  };
  const Descontar = () => {
    const timer = setTimeout(() => {
      setContador(contador - 1);
    }, 1000);
  };

  useEffect(() => {
    if (contador === 0) {
      history.push("/");
    } else {
      Descontar();
    }
  }, [contador]);

  return (
    <div>
      <h1>{contador}</h1>
      <Button onClick={AddTime} variant="outline-primary">
        Add Time
      </Button>
    </div>
  );
}

这是沙箱,所以更容易看到示例:https://codesandbox.io/s/awesome-wiles-oqh71?file=/src/App.js

【问题讨论】:

  • 尝试在调用AddTime 时清除超时。所以当地。当它在本地范围内使用旧值时,它不是 useEffect 的问题,而是超时功能

标签: reactjs react-hooks


【解决方案1】:

这是covered in the React Hooks FAQ——你看到的是陈旧的道具。解决方法是使用 ref 将值“装箱”并在钩子回调函数中从框中读取它。

react-use library has a handy useLatest hook 来解决这个问题;如果您最终添加react-use,您还可以查看例如useTimeoutFn 来自它。

但是,在这种情况下,最简单的解决方法是使用函数形式让 React 将当前的 contador 值传递给您并相应地修改它:

setContador(contador => contador - 1);

编辑:

这是一个使用单个间隔计时器的完整示例:

import React from "react";
import { useHistory } from "react-router-dom";
import Button from "react-bootstrap/Button";

export default function PagTimer() {
  const history = useHistory();
  const [contador, setContador] = React.useState(10);
  React.useEffect(() => {
    const timer = setInterval(() => {
      setContador((contador) => contador - 1);
    }, 1000);
    return () => clearInterval(timer);
  }, []);
  const AddTime = React.useCallback(() => {
    setContador(10);
  }, []);

  React.useEffect(() => {
    if (contador === 0) {
      history.push("/");
    }
  }, [contador, history]);

  return (
    <div>
      <h1>{contador}</h1>
      <Button onClick={AddTime} variant="outline-primary">
        Add Time
      </Button>
    </div>
  );
}

【讨论】:

  • 太棒了!感谢您的资源,稍后将阅读更多相关信息:)
【解决方案2】:

问题是 - 每当状态发生变化时,它都会使用 setTimeout 创建一个新的计时器,而不会破坏之前的计时器。

解决方案-

您可以使用 useEffect 提供的清理函数(类似于 ComponentDidUnmount 函数),该函数在组件被卸载或销毁时被调用。

  const Descontar = () => {
    const timer = setTimeout(() => {
      setContador(contador - 1);
    }, 1000);
    return timer;
  };

  useEffect(() => {
    var timer;
    if (contador === 0) {
      //history.push("/");
    } else {
      timer = Descontar();
    }
    return () => {
      clearInterval(timer);
    };
  }, [contador]);

要使上述方法起作用,您需要保存/返回计时器。

或者你可以在AddTime函数中清除定时器

工作沙盒链接 - https://codesandbox.io/s/trusting-chebyshev-kziey?file=/src/App.js

【讨论】:

  • setTimeout 是一次性的,你不需要销毁它们。
  • @Tethys0 谢谢!我将阅读有关干净间隔函数的信息,以便我了解发生了什么。不过谢谢!它现在正在工作:)
  • @AKX 如果您可以查看代码,每次组件更新时,它都会创建一个新的 SetTimeout,它会在 1 秒后回调 fn“setContador”。一击如何?对于每个状态更新,它都会回调我认为没用的 fn,并且不应让此类计时器悬空。不使用最好销毁。
  • 另外,代码正在运行,清除超时没有问题,还有其他不赞成投票的理由吗?
  • 不过,您确实应该像我的回答一样使用set... 的函数形式。这只有效,因为每次组件重新渲染时,Descontar() 恰好使用正确的封闭(闭包)值 contador 重新创建。
猜你喜欢
  • 2019-08-30
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 2022-01-23
  • 2020-05-12
  • 2021-02-09
  • 2019-01-18
相关资源
最近更新 更多