繁体   English   中英

为什么将函数参数传递给 React.useState 并且返回函数将返回陈旧值

[英]Why is passing function argument to React.useState and the return function will return stale value

给出下面的代码

import { useState } from "react";
import "./styles.css";

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

  const inc1 = () => {
    console.log("debug inc:", count1);
    setCount1((prev) => prev + 1);
  };

  const inc2 = () => {
    console.log("debug inc2:", count2);
    setCount2(count2 + 1);
  };

  const [processInc1] = useState(() => {
    console.log("debug longProcessBeforeInc:", count1);
    // Run some long process here
    return inc1;
  });

  const [processInc2] = useState(() => {
    console.log("debug longProcessBeforeInc:", count2);
    // Run some long process here
    return inc2;
  });

  console.log("debug render:", count1, count2);

  return (
    <div className="App">
      <h3>
        {count1} - {count2}
      </h3>
      <button onClick={inc1}>Inc 1</button>
      <br />
      <br />
      <button onClick={inc2}>Inc 2</button>
      <br />
      <br />
      <button onClick={processInc1}>Long Inc 1</button>
      <br />
      <br />
      <button onClick={processInc2}>Long Inc 2</button>
    </div>
  );
}

inc1inc2processInc1都按预期工作,您将值增加 1 并正确呈现。

所以inc1 , inc2的主要区别是setCount1((prev) => prev + 1); setCount2(count2 + 1); , 和processInc1 , processInc2基本上在组件第一次渲染时分别通过 useState 返回inc1inc2

我从这里了解到https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function它与关闭有关,但是鉴于上面的示例,我无法理解为什么inc2processInc1有效但processInc2无效?

这是上述https://codesandbox.io/s/eloquent-morse-37xyoy?file=/src/App.jscodesandbox链接

你走在正确的道路上。 正如您所说,这个问题实际上是由使用闭包引起的。 因此,让我们首先向您展示一个很好的闭包定义:

闭包是函数和声明该函数的词法环境的组合。 该环境由创建闭包时在范围内的任何局部变量组成。

文档: https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

这意味着当您在 useState 中返回闭包inc2时,您还创建了该词法环境在特定时间(即初始呈现)使用的变量的副本(这包括 count2 值)。 这就是为什么processInc2将保持相同的旧 count2 值。

同时processInc1函数将正常工作,因为通过在 useState 中使用回调,您始终可以获取 count1 状态的当前值。

最后, inc2有效,因为您在单击按钮时直接调用它,因此在您调用它的那一刻计算 count2 值(因此它很可能具有当前值)。

这里一个非常重要的细节是,每次App组件渲染时都会重新定义inc1inc2

function App() {
  // ...

  const inc1 = () => {
    console.log("debug inc:", count1);
    setCount1((prev) => prev + 1);
  };

  // ...
}

然后,您将这两个函数存储在一个永远不会改变的状态中。

const [processInc1] = useState(() => {
  console.log("debug longProcessBeforeInc:", count1);
  // Run some long process here
  return inc1;
});

这将导致processInc1processInc2指向inc1inc2的第一个定义(在第一次渲染时创建)。

在函数的第一个版本中, count1count2永远不会更新的原因是因为永远不会重新分配变量。 这是设计使然。

count1count2在未来渲染中发生变化的唯一原因是useState()将返回新值。 收到这个新值后, inc1inc2被重新定义。

然后processInc1processInc2被拉出包含inc1inc2的第一个定义的 React 状态,因此在这些函数中使用count1count2将引用count1count2的第一个值。

当您在 inc2 中执行inc2 setCount2(count2 + 1)并通过processInc2调用它时, count2的值仍然是0并且永远不会改变。 这是因为processInc2指的是inc2的第一个定义,而不是当前定义。

setCount1((prev) => prev + 1)由于不同的函数签名而起作用。 其中inc2将静态值 ( 0 + 1 ) 传递给 setter,而inc1则传递转换(作为回调)。 当你将一个函数传递给setCount1时,React 会以当前状态作为唯一参数调用该函数。 然后将返回值用作新状态。 因此,尽管processInc1仍然使用inc1的第一个定义。 它总是相关的,因为它描述了必须进行的转换,而不是必须设置的值。

请注意console.log("debug inc:", count1); 由于上述原因,通过processInc1调用时将保持记录0

暂无
暂无

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

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