繁体   English   中英

React useState 不更新值

[英]React useState does not update value

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

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>;
}

反应文档说:

问题是在 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>;
}

还有很多事情要做,并且需要对语言进行更多解释,以最好地准确解释它的工作原理以及它为什么会这样工作,尽管我将重点放在您的示例上以使其保持简单。

useRef变得简单

function Counter() {
  const countRef = useRef(0);

  useEffect(() => {
    const id = setInterval(() => {
      countRef.current++;
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <h1>{countRef.current}</h1>;
}

暂无
暂无

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

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