React useState 不更新值

par*_*aks 3 javascript reactjs react-hooks use-effect use-state

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

\n
function Counter() {\n  const [count, setCount] = useState(0);\n\n  useEffect(() => {\n    const id = setInterval(() => {\n      setCount(count + 1); // This effect depends on the `count` state\n    }, 1000);\n    return () => clearInterval(id);\n  }, []); //  Bug: `count` is not specified as a dependency\n\n  return <h1>{count}</h1>;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但重写如下:

\n
function Counter() {\n  const [count, setCount] = useState(0);\n  let c = count;\n  useEffect(() => {\n    const id = setInterval(() => {\n      setCount(c++);\n    }, 1000);\n    return () => clearInterval(id);\n  }, []);\n\n  return <h1>{count}</h1>;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

React 文档说:

\n
\n

问题是在 setInterval 回调内部,count 的值没有改变,因为我们创建了一个闭包,其中 count 的值设置为 0,就像效果回调运行时一样。每一秒,这个回调都会调用setCount(0 + 1),因此计数永远不会超过 1。

\n
\n

但这个解释没有意义。那么为什么第一个代码没有正确更新计数,而第二个代码却可以呢?\n(也声明为let [count, setCount] = useState(0)then usingsetCount(count++)也可以正常工作)。

\n

Emi*_*ron 10

为什么看起来不起作用?

\n

有一些提示可以帮助您了解正在发生的情况。

\n

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

\n

count在回调中使用时,闭包会捕获变量并count保持可用,即使组件函数已完成执行也是如此。同样,这很令人困惑useEffect,因为看起来回调是在每个渲染周期创建的,捕获最新的count值,但事实并非如此。

\n

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

\n

挂载时

\n
function Counter() {\n  const [count_0, setCount_0] = useState(0);\n\n  useEffect(\n    // This is defined and will be called after the component is mounted.\n    () => {\n      const id_0 = setInterval(() => {\n        setCount_0(count_0 + 1);\n      }, 1000);\n      return () => clearInterval(id_0);\n    }, \n  []);\n\n  return <h1>{count_0}</h1>;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

一秒钟后

\n
function Counter() {\n  const [count_1, setCount_1] = useState(0);\n\n  useEffect(\n    // completely ignored by useEffect since it\'s a mount \n    // effect, not an update.\n    () => {\n      const id_0 = setInterval(() => {\n        // setInterval still has the old callback in \n        // memory, so it\'s like it was still using\n        // count_0 even though we\'ve created new variables and callbacks.\n        setCount_0(count_0 + 1);\n      }, 1000);\n      return () => clearInterval(id_0);\n    }, \n  []);\n\n  return <h1>{count_1}</h1>;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

为什么它可以与 一起使用let c

\n

let使得重新分配给 成为可能c,这意味着当它被 ouruseEffectsetInterval闭包捕获时,它仍然可以像它存在一样使用,但它仍然是第一个定义的。

\n

挂载时

\n
function Counter() {\n  const [count_0, setCount_0] = useState(0);\n\n  let c_0 = count_0;\n\n  // c_0 is captured once here\n  useEffect(\n    // Defined each render, only the first callback \n    // defined is kept and called once.\n    () => {\n      const id_0 = setInterval(\n        // Defined once, called each second.\n        () => setCount_0(c_0++), \n        1000\n      );\n      return () => clearInterval(id_0);\n    }, \n    []\n  );\n\n  return <h1>{count_0}</h1>;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

一秒钟后

\n
function Counter() {\n  const [count_1, setCount_1] = useState(0);\n\n  let c_1 = count_1;\n  // even if c_1 was used in the new callback passed \n  // to useEffect, the whole callback is ignored.\n  useEffect(\n    // Defined again, but ignored completely by useEffect.\n    // In memory, this is the callback that useEffect has:\n    () => {\n      const id_0 = setInterval(\n        // In memory, c_0 is still used and reassign a new value.\n        () => setCount_0(c_0++),\n        1000\n      );\n      return () => clearInterval(id_0);\n    }, \n    []\n  );\n\n  return <h1>{count_1}</h1>;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

使用 Hook 的最佳实践

\n

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

\n
// \xe2\x9d\x8c Avoid using the captured count.\nsetCount(count + 1)\n\n// \xe2\x9c\x85 Use the latest state with the updater function.\nsetCount(currCount => currCount + 1)\n
Run Code Online (Sandbox Code Playgroud)\n

在代码中:

\n
function Counter() {\n  const [count, setCount] = useState(0);\n\n  useEffect(() => {\n    // I chose a different name to make it clear that we\'re \n    // not using the `count` variable.\n    const id = setInterval(() => setCount(currCount => currCount + 1), 1000);\n    return () => clearInterval(id);\n  }, []);\n\n  return <h1>{count}</h1>;\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

还有更多的事情发生,并且需要更多的语言解释来最好地解释它是如何工作的以及为什么它这样工作,尽管我将其重点放在您的示例上以保持简单。

\n\n