简体   繁体   English

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

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

Give the codes below给出下面的代码

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

inc1 , inc2 , processInc1 all works as expected where you increase value by 1 and it renders correctly. inc1inc2processInc1都按预期工作,您将值增加 1 并正确呈现。

So with inc1 , inc2 the main difference is setCount1((prev) => prev + 1);所以inc1 , inc2的主要区别是setCount1((prev) => prev + 1); and setCount2(count2 + 1);setCount2(count2 + 1); , and processInc1 , processInc2 basically return inc1 and inc2 respectively via useState first time the component renders. , 和processInc1 , processInc2基本上在组件第一次渲染时分别通过 useState 返回inc1inc2

I understand from here https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function that it has something to do with closure but given the example above I fail to wrap my head around why inc2 , and processInc1 works but not processInc2 ?我从这里了解到https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function它与关闭有关,但是鉴于上面的示例,我无法理解为什么inc2processInc1有效但processInc2无效?

Here is the link to the codesandbox for the abovehttps://codesandbox.io/s/eloquent-morse-37xyoy?file=/src/App.js这是上述https://codesandbox.io/s/eloquent-morse-37xyoy?file=/src/App.jscodesandbox链接

You are on the right path.你走在正确的道路上。 As you were saying, this problem is actually caused by the use of the closure.正如您所说,这个问题实际上是由使用闭包引起的。 So let's start by showing you a nice definition of closure:因此,让我们首先向您展示一个很好的闭包定义:

A closure is the combination of a function and the lexical environment within which that function was declared.闭包是函数和声明该函数的词法环境的组合。 This environment consists of any local variables that were in-scope at the time the closure was created.该环境由创建闭包时在范围内的任何局部变量组成。

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

This means that when you return the closure inc2 in the useState, you are also creating a copy of the variables that are used at that particular time (ie initial rendering) by that lexical environment (this includes the count2 value).这意味着当您在 useState 中返回闭包inc2时,您还创建了该词法环境在特定时间(即初始呈现)使用的变量的副本(这包括 count2 值)。 This is why processInc2 will keep having the same old count2 value.这就是为什么processInc2将保持相同的旧 count2 值。

At the same time processInc1 function will work correctly because, by using a callback in the useState, you are always getting the current value of the count1 state.同时processInc1函数将正常工作,因为通过在 useState 中使用回调,您始终可以获取 count1 状态的当前值。

Lastly, inc2 works because you are directly calling it when you click the button, so the count2 value gets evaluated at that moment that you call it (therefore it will most probably have the current value).最后, inc2有效,因为您在单击按钮时直接调用它,因此在您调用它的那一刻计算 count2 值(因此它很可能具有当前值)。

A very important detail here is that inc1 and inc2 are re-defined each time the App component renders.这里一个非常重要的细节是,每次App组件渲染时都会重新定义inc1inc2

function App() {
  // ...

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

  // ...
}

You then store those 2 functions inside a state, that does never change.然后,您将这两个函数存储在一个永远不会改变的状态中。

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

This will result in processInc1 and processInc2 that point to the very first definition of inc1 and inc2 (created on the first render).这将导致processInc1processInc2指向inc1inc2的第一个定义(在第一次渲染时创建)。

The reason count1 and count2 never update in this first version of the function is because the variable(s) are never re-assigned.在函数的第一个版本中, count1count2永远不会更新的原因是因为永远不会重新分配变量。 This is by design.这是设计使然。

The only reason count1 and count2 change in a future render is because useState() will return the new value. count1count2在未来渲染中发生变化的唯一原因是useState()将返回新值。 After you receive this new value inc1 and inc2 are re-defined.收到这个新值后, inc1inc2被重新定义。

processInc1 and processInc2 are then pulled out of a React state that holds the first definition of inc1 and inc2 , so usage of count1 and count2 inside those functions will refer to the first value of count1 and count2 .然后processInc1processInc2被拉出包含inc1inc2的第一个定义的 React 状态,因此在这些函数中使用count1count2将引用count1count2的第一个值。

When you do setCount2(count2 + 1) inside inc2 and call it via processInc2 the value of count2 is still 0 and will never change.当您在 inc2 中执行inc2 setCount2(count2 + 1)并通过processInc2调用它时, count2的值仍然是0并且永远不会改变。 This is because processInc2 refers to the very first definition of inc2 , and not the current definition.这是因为processInc2指的是inc2的第一个定义,而不是当前定义。

setCount1((prev) => prev + 1) works due to the different function signature. setCount1((prev) => prev + 1)由于不同的函数签名而起作用。 Where inc2 passes a static value ( 0 + 1 ) to the setter, inc1 passes a transformation (as a callback).其中inc2将静态值 ( 0 + 1 ) 传递给 setter,而inc1则传递转换(作为回调)。 When you pass a function to setCount1 React will call that function with the current state as the sole argument.当你将一个函数传递给setCount1时,React 会以当前状态作为唯一参数调用该函数。 The return value is then used as the new state.然后将返回值用作新状态。 So although processInc1 still uses the very first definition of inc1 .因此,尽管processInc1仍然使用inc1的第一个定义。 It will always be relevant, since it describes the transformation that must be made rather than the value that must be set.它总是相关的,因为它描述了必须进行的转换,而不是必须设置的值。

Do note that console.log("debug inc:", count1);请注意console.log("debug inc:", count1); will keep logging 0 when called via processInc1 for the above reasons.由于上述原因,通过processInc1调用时将保持记录0

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

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