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

Zhe*_*Bai 6 javascript reactjs react-hooks use-effect

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

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

否则,您的代码将引用先前渲染中的陈旧值。

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

经验丰富的 JavaScript 开发人员可能会注意到,传递给 useEffect 的函数在每次渲染时都会有所不同。这是故意的。事实上,这让我们可以从效果内部读取计数值,而不必担心它会过时。

据我的理解,如果我们错过列出依赖项,则该效果将不会在该依赖项更改引起的渲染后运行,因为 React 认为效果不依赖于它。如果我猜测,可能是文档提到引用过时数据时的情况。事实上,效果代码中的数据已经过时。但是,效果回调一开始并不运行。在效果运行之前我不会注意到数据已过时。如果这很重要,我将首先找出效果未运行的原因并解决问题。我感到困惑的不是数据过时,而是效果无法运行。

此外,我们假设效果在由另一个依赖项更改引起的渲染之后运行。在这种情况下,即使我们错过了依赖项,由于上述关闭原因,我们也不会读取过时的数据。我做了一些实验来证实这一点

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>
  );
}
Run Code Online (Sandbox Code Playgroud)

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

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

Kei*_*ith 4

我稍微修改了您的示例以显示陈旧的值。

效果通常出于异步原因而使用,因此这并不罕见。

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

单击count1then 意味着再次调用 useEffect,创建 setInterval 的新实例,并使用 count1 和 count2 的新(非陈旧)副本。因为 count2 不在依赖数组中,所以单击count2将意味着不会创建新的 setInterval,并且 count1 和 count2 的过时副本将保留在内存中。

公平地说,这可能是 Hooks 中难以理解的一个领域。很容易将 Hook 组件视为对象的一部分,其中包含数据的类。但实际上钩子组件只是渲染函数,useState / useEffect 等是侧面加载到渲染函数管道中的。相比之下,React 类组件的数据与对象实例一起存储,因此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'));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)