React useState不更新值 [英] React useState does not update value

查看:0
本文介绍了React useState不更新值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有点困惑,为什么这个组件没有按预期工作:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // This effect depends on the `count` state
    }, 1000);
    return () => clearInterval(id);
  }, []); // 🔴 Bug: `count` is not specified as a dependency

  return <h1>{count}</h1>;
}

但如下所示的重写有效:

function Counter() {
  const [count, setCount] = useState(0);
  let c = count;
  useEffect(() => {
    const id = setInterval(() => {
      setCount(c++);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <h1>{count}</h1>;
}

Reaction文档显示:

问题在于,在setInterval回调中,count的值没有更改,因为我们已经创建了一个闭包,将count的值设置为0,就像运行效果回调时一样。此回调每秒钟调用一次setCount(0 + 1),因此计数永远不会超过1。

但这个解释说不通。那么,为什么第一个代码不能正确更新,而第二个代码可以呢? (也可以声明为let [count, setCount] = useState(0),然后使用setCount(count++)也可以)。

推荐答案

为什么看起来不起作用?

有几个提示可以帮助您了解正在发生的事情。

countconst,因此它的作用域永远不会改变。它令人困惑,因为它在调用setCount时看起来正在更改,但它永远不会更改,只是再次调用该组件并创建一个新的count变量。

在回调中使用count时,闭包捕获变量,并且count保持可用,即使组件函数已执行完毕。同样,它与useEffect混淆,因为它看起来像是在每个呈现周期创建回调,捕获最新的count值,但实际情况并非如此。

为清楚起见,让我们在每次创建变量时为它们添加一个后缀,并查看发生了什么。

在挂载时

function Counter() {
  const [count_0, setCount_0] = useState(0);

  useEffect(
    // This is defined and will be called after the component is mounted.
    () => {
      const id_0 = setInterval(() => {
        setCount_0(count_0 + 1);
      }, 1000);
      return () => clearInterval(id_0);
    }, 
  []);

  return <h1>{count_0}</h1>;
}

一秒后

function Counter() {
  const [count_1, setCount_1] = useState(0);

  useEffect(
    // completely ignored by useEffect since it's a mount 
    // effect, not an update.
    () => {
      const id_0 = setInterval(() => {
        // setInterval still has the old callback in 
        // memory, so it's like it was still using
        // count_0 even though we've created new variables and callbacks.
        setCount_0(count_0 + 1);
      }, 1000);
      return () => clearInterval(id_0);
    }, 
  []);

  return <h1>{count_0}</h1>;
}

为什么与let c一起使用?

let允许重新分配给c,这意味着当它被我们的useEffectsetInterval闭包捕获时,它仍然可以被使用,就像它存在一样,但它仍然是第一个定义的。

在挂载时

function Counter() {
  const [count_0, setCount_0] = useState(0);

  let c_0 = count_0;

  // c_0 is captured once here
  useEffect(
    // Defined each render, only the first callback 
    // defined is kept and called once.
    () => {
      const id_0 = setInterval(
        // Defined once, called each second.
        () => setCount_0(c_0++), 
        1000
      );
      return () => clearInterval(id_0);
    }, 
    []
  );

  return <h1>{count_0}</h1>;
}

一秒后

function Counter() {
  const [count_1, setCount_1] = useState(0);

  let c_1 = count_1;
  // even if c_1 was used in the new callback passed 
  // to useEffect, the whole callback is ignored.
  useEffect(
    // Defined again, but ignored completely by useEffect.
    // In memory, this is the callback that useEffect has:
    () => {
      const id_0 = setInterval(
        // In memory, c_0 is still used and reassign a new value.
        () => setCount_0(c_0++),
        1000
      );
      return () => clearInterval(id_0);
    }, 
    []
  );

  return <h1>{count_1}</h1>;
}

挂钩的最佳实践

由于很容易与所有回调和计时混淆,并且为了避免任何意外的副作用,最好使用函数式更新器状态设置器参数。

// ❌ Avoid using the captured count.
setCount(count + 1)

// ✅ Use the latest state with the updater function.
setCount(currCount => currCount + 1)

在代码中:

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // I chose a different name to make it clear that we're 
    // not using the `count` variable.
    const id = setInterval(() => setCount(currCount => currCount + 1), 1000);
    return () => clearInterval(id);
  }, []);

  return <h1>{count}</h1>;
}

还有很多事情要做,需要对该语言进行更多的解释,才能准确地解释它是如何工作的,以及为什么它是这样工作的,尽管为了简单起见,我将重点放在了您的示例上。

这篇关于React useState不更新值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆