在这个基于 React 的表单中,我对 `setValues` 的情况感到困惑。能帮忙详细说说吗?

In this React based form, I am confused by what's going on with `setValues`. Can you help elaborate?

首先,我们有一个基于反应的小型表单,其中包含三个字段(名字、姓氏、电子邮件),后跟一个“注册”按钮。现在,笔者正在使用下面的代码来组织状态。

const [values, setValues] = useState({
    firstName: '',
    lastName: '',
    email: '',
});

然后作者使用以下代码从字段 onChange 中捕获键入的值。但是,我无法完全理解发生了什么。我希望有人能帮我解释一下。

const handleFirstNameInputChange = (event) => {
    event.persist();
    setValues((values) => ({
        ...values,
        firstName: event.target.value,
    }));
};

我对这里 setValues 的情况感到特别困惑。例如,为什么我们要尝试在这种情况下“传播”价值观?为什么firstName跟在spread之后呢?为什么匿名函数体要用圆括号和大括号包裹起来?

非常感谢。

setValues 是来自 React 状态的 setter。它会设置您的电子邮件、名字和密码。

应该更像这样

const handleChange = (event) => {
    event.persist();
    
    // the name here would be the key of your state object.
    // i.e email, password and firstname
   // it should be defined as the name in your input field.
    const name = event.target.name;
    const value = event.target.value;
    
    setValues((values) => ({
        ...values,
        [name]: value,
    }));
};

您可以像这样使用您的字段

// the name "email" has to be passed through here.
// you could access both the value and the key in your `handleChange` handler
<input type="email" name="email" onChange={handleChange} value={values.email} />

您可以查看工作示例here

React 可以同步或异步设置状态。这意味着当您调用 setValues() 时,您的状态不一定会立即改变。相反,您的状态可能会在几毫秒后更新。由 React 的 inner-workings 决定何时更新状态。

因此,React 允许我们在设置状态时使用回调。回调将使您能够访问之前的状态,然后您可以通过 returning 从回调中使用更新后的状态。在您的 setValues() 方法中考虑到这一点:

setValues((values) => ({
  ...values,
  firstName: event.target.value,
}));  

上方 values 是您之前的状态 - 所以是一个对象。当您将一个对象传播到另一个对象中时,您将从 values 对象中获取所有可枚举(自己的)键并将它们添加到您传播到的新对象中。您可以将此视为将一个对象的属性合并到一个新对象中:

const a = {
  aKey: 1,
  bKey: 1.5
}

const b = {
  ...a,
  bKey: 2
}

/*
b is interpreted as:
{
  akey: 1,
  bKey: 1.5,
  bKey: 2 <-- Objects can't have duplicate keys, so this one overwrites the above
}

... which then evaluates to:
{
  "aKey": 1,
  "bKey": 2
}

*/

console.log(b);

你的回调然后 returns 新更新的对象(因为这是一个箭头函数,它是一个隐式的 return)。请注意,该对象包含在括号 ( ) 中。这是因为如果没有括号,对象字面量的 { } 将被解释为 code-block(因此代码将被解释为 function-body)而不是对象字面量。

// Arrow function with a body (ie: code-block)
// code-block --- \/
const foo = () => {

};

// Arrow function with implict return
const bar = () => ({ // <--- object

});

关于 event.persist() 的注释。

React 使用 SyntheticEvents. A SyntheticEvent is a wrapper to the native event object which gets passed to your event handler. React uses this wrapper for cross-browser compatibility. However, SyntheticEvents are a little different to native events as they're "pooled"。池化本质上意味着 SyntheticEvents 被重用,这意味着 event 的相同对象引用可用于整个应用程序中的其他事件。因此,对象在使用完毕后需要“无效化”。 “使”对象无效意味着在 event-handler 完成后使对象的键值变为 null。所以事件对象的键仍然存在,但是,键的值设置为 null。这样,当另一个事件触发时,React 可以获取 SythenticEvent 并填充它的值。 React 使用这种池的概念,因为为每个事件创建一个新的 SyntheticEvent 实例的成本可能很高,因此,使用引用更有效,因为不需要为每个事件创建一个新实例。

react之所以取消事件是因为性能原因。当值设置为 null 时,可以对旧值进行垃圾回收,这将 free-up 应用程序的内存。

因此,一旦event-handler 执行完毕,event 的属性将被取消。如前所述,setValues() 可能是异步的,这意味着它可能 运行 您的事件处理程序完成执行并使事件对象无效之后。因此,执行 event.target.value 会由于 event 无效而导致错误。因此,要阻止此错误的发生,我们可以使用 event.persist() 阻止事件被合并并因此被取消。现在 event 不会被重用,因此不需要将其作废。因此,另一个合成事件将被添加到池中以取代旧事件。对于您的情况,保留整个事件对象有点​​ over-kill,因为您想要保留的只是 event.target.value。这可以通过将 event.target.value 保存在变量中来完成。

const firstName = event.target.value;

当对象无效时,这不会影响我们的 firstName 变量,因为它存储的是一个值,而不是对对象内值的引用。这样,当你设置状态时,你的回调可以关闭这个变量,因为它很好用:

const firstName = event.target.value;
setValues((values) => ({
  ...values,
  firstName
})); 

事件池不是你需要担心太久的东西,因为它将不再用于 React 17(因为它似乎不会提高现代浏览器的性能)。