useEffect() 中缺少依赖项真的会导致数据过时吗?

Missing dependencies in useEffect() really cause stale data?

我问这个问题是为了确认我对某些概念的理解。

React 文档强调包含 useEffect() 回调中使用的所有依赖项。如文档中所述:

Otherwise, your code will reference stale values from previous renders.

我有点明白这个解释是从哪里来的了。但让我担心的是“陈旧价值”部分。我看不到任何可能由于缺少依赖项而导致陈旧值发生的方式。我的论点也得到了文档内容的支持:

Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. This is intentional. In fact, this is what lets us read the count value from inside the effect without worrying about it getting stale.

据我了解,如果我们错过列出依赖项,效果将不会 运行 在由该依赖项更改引起的渲染之后,因为 React 认为效果不依赖于它。如果我猜测,可能是文档提到引用陈旧数据时的情况。事实上,该数据在效果代码中已经过时。然而,效果回调一开始并不是 运行 。在效果 运行 之前,我不会注意到数据已过时。如果这很重要,我将首先弄清楚为什么效果没有 运行 并解决问题。我感到困惑的不是数据陈旧,而是效果不会 运行.

更重要的是,我们假设 运行 秒后的效果是由另一个依赖项更改引起的。在这种情况下,即使我们错过了依赖项,由于上述关闭原因,我们也不会读取过时的数据。 I experimented a bit to confirm this:

export default function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(10);

  useEffect(() => {
    console.log(count2);
  }, [count1]);
  
  return (
    <div className="App">
      <div>
        Count1: {count1}
        <button onClick={() => setCount1(count1 + 1)}>increase</button>
      </div>
      <div>
        Count2: {count2}
        <button onClick={() => setCount2(count2 + 1)}>increase</button>
      </div>
    </div>
  );
}

我们总是得到最新的count2,只要效果运行s。那么我的理解是否成立?

我想知道为什么 React 如此推荐包含所有依赖项。人们通常使用依赖数组来绕过某些效果 运行ning。如果他们省略了依赖项,那很可能就是他们想要的。如果是失误,他们会很容易注意到影响 运行ning 并采取行动。

您的示例代码展示了最简单的示例,在极其简单的场景中。默认情况下,useEffect 将在您的组件的每次重新渲染时 运行。使用依赖数组,当其中一个值发生变化时,内部函数仅 运行s。我个人 运行 遇到过这样的场景:我忘记了一个依赖项,我的效果被触发了,我的函数中的一个值有陈旧的数据。这可能是因为当一个变化触发了效果时,有几个过程同时进行,而另一部分数据还没有跟上。切换到使用 useReducer 来同时控制多个状态位,而不是多个 useState,在某些情况下有所帮助,但最终依赖数组使其保持一致。此外(我还没有证实这一点),useEffect 的框架代码可能大量使用了闭包,因此,再次强调,这是关于确保它在过程中的正确时刻引用数据点。

我稍微修改了您的示例以显示过时的值。

Effects 通常用于异步原因,所以这并不罕见。

基本上它取决于闭包,useEffect 的第一个渲染将在 count1 和 count2 上创建一个闭包,如果效果不是对所有依赖项重新运行,那么这些闭包将保持(陈旧)。

单击 count1 意味着再次调用 useEffect,创建 setInterval 的新实例,其中包含 count1 和 count2 的新(none 陈旧)副本。因为 count2 不在依赖数组中,单击 count2 将意味着不会创建新的 setInterval,count1 和 count2 的陈旧副本将保留在内存中。

公平地说,这可能是 Hooks 中一个难以理解的领域。很容易将 Hook Components 想象成 类 with data 是 Object 的一部分。但实际上 hook 组件只是渲染函数,useState / useEffect 等是加载到渲染函数管道中的一种方式。相比之下,React Class 组件将其数据与对象实例一起存储,因此 this.xyz 永远不会过时。

const {useState, useEffect} = React;


function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(10);

  useEffect(() => {
    const tm = setInterval(() => {
      console.log(count1, count2);
    }, 1000);
    return () => clearInterval(tm);
  }, [count1]);
  
  return (
    <div className="App">
      <div>
        Count1: {count1}
        <button onClick={() => setCount1(count1 + 1)}>increase</button>
      </div>
      <div>
        Count2: {count2}
        <button onClick={() => setCount2(count2 + 1)}>increase</button>
      </div>
    </div>
  );
}

ReactDOM.render(<App/>,document.querySelector('#mount'));
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="mount"></div>

<p>Increase count2, see the console not update until you increase count1,.</p>
<p>Add count2 to the dependancy, and then everything will keep in sync</p>