• 为什么不建议把React表单数据存储在状态变量中
  • 发布于 2个月前
  • 214 热度
    0 评论
说到在React中处理表单,最流行的方法是将输入值存储在状态变量中。遵循这种方法的原因之一是因为毕竟它是React,每个人都倾向于使用它附带的hooks。使用hooks可以解决React中的许多问题,但是在处理表单时是否必需呢?让我们来看看。

使用“States”存在问题
正如我们已经知道的那样,每当组件内的状态变量的值发生变化时,React都会重新渲染组件以匹配其当前状态。虽然在小型应用程序中这不是一个大问题,但随着应用程序规模的增长,它可能导致性能瓶颈。当涉及到表单时,React会尝试在每次输入(状态)发生变化时重新渲染组件。

小提示:我在StackOverflow上找到了一个非常有用的答案,可以用来计算组件渲染的次数。我们也会在我们的代码中使用这个实用函数。

使用Vite创建一个基本的React应用,并在项目创建后清理掉不需要的文件。
npm create vite@latest my-vue-app  --template react
# yarn
yarn create vite my-vue-app --template react
# pnpm
pnpm create vite my-vue-app --template react
让我们创建一个 React 组件(称为 FormWithState ),其中包含一个表单,该表单接受两个输入:电子邮件和密码。我们将使用状态来管理表单输入。
import { useEffect, useRef, useState } from "react";
import "./Forms.css";

export default function FormWithState() {
  // The count will increment by 2 on initial render due to strict mode then by 1 on subsequent renders
  const countRef = useRef(0);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  useEffect(() => {
    countRef.current = countRef.current + 1;
  });

  function handleSubmit(e) {
    e.preventDefault();
    console.log({ email, password });
  }

  return (
    <div className="form-div">
      <h2>Form With State</h2>
      <form onSubmit={handleSubmit}>
        <div className="input-field">
          <label htmlFor="email2">Email</label>
          <input
            id="email2"
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            autoComplete="off"
          />
        </div>
        <div className="input-field">
          <label htmlFor="password2">Password</label>
          <input
            id="password2"
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </div>
        <button type="submit">Submit</button>
        <div>
          <p>
            The Component Re-Rendered <span>{countRef.current}</span> times
          </p>
        </div>
      </form>
    </div>
  );
}
将此组件添加到 App 组件中,并打开 http://localhost:5173

正如你所看到的,表单组件大约被渲染了23次,随着输入字段数量的增加,这个数字会逐渐增加。在大多数情况下,表单值仅在表单提交时使用。那么,难道为了两个输入字段就需要重新渲染20多次的组件吗?答案是明确的:不需要!

此外,当输入字段的数量增加时,存储输入值的状态变量的数量也会增加,从而增加了代码库的复杂性。那么,有没有其他方法可以避免重新渲染,同时实现表单的所有功能呢?

使用FormData来处理表单
所以,另一种方法是使用JavaScript的原生 FormData 接口。根据官方文档的描述,创建一个新的 FormData 对象有三种方法。
new FormData();
new FormData(form);
new FormData(form, submitter);
我们将使用第二种方法,因为我们已经有一个表单。我们只需要将表单元素传递给构造函数,它将自动填充表单值。为了使其工作,我们还需要在 input 标签中添加 name 属性。让我们测试一下这种方法。创建一个组件(比如 FormWithoutState )。
import { useEffect, useRef } from "react";

export default function FormWithoutState() {
  // 堆代码 duidaima.com
  // The count will increment by 2 on initial render due to strict mode then by 1 on subsequent renders
  const countRef = useRef(0);

  useEffect(() => {
    countRef.current = countRef.current + 1;
  });

  function handleSubmit(e) {
    e.preventDefault();
    const form = new FormData(e.currentTarget);
    const email = form.get("email");
    const password = form.get("password");
    console.log({ email, password });
    const body = {};
    for (const [key, value] of form.entries()) {
      body[key] = value;
    }
    console.log(body);
    // Do Further input validation and submit the form
  }

  return (
    <div className="form-div">
      <h2>Form Without State</h2>
      <form onSubmit={handleSubmit}>
        <div className="input-field">
          <label htmlFor="email1">Email</label>
          <input id="email1" type="email" name="email" autoComplete="off" />
        </div>
        <div className="input-field">
          <label htmlFor="password1">Password</label>
          <input id="password1" type="password" name="password" />
        </div>
        <button type="submit">Submit</button>
        <div>
          <p>
            The Component Re-Rendered <span>{countRef.current}</span> times
          </p>
        </div>
      </form>
    </div>
  );
}
在这个组件中,我们根本没有使用 useState hook。相反,我们将 name 属性添加到 input 标签中。一旦用户提交表单,在 handleSubmit 函数中,我们通过 e.currentTarget 提供表单对象来创建 FormData 。然后,我们通过 FormData.entries() 方法迭代获取表单的键和值来构建表单主体。我们可以使用这个对象进行进一步的输入验证和通过 fetch 或 Axios API进行提交。但是,这种方法对组件重新渲染的影响如何呢?让我们来看看。将这个组件添加到 App 组件中,并打开 http://localhost:5173 。

你难道不觉得惊讶吗?这个组件根本没有重新渲染。

使用FormData的优势
表单输入值会自动捕获,无需为每个输入字段维护状态变量。使用 FormData 时,API请求体可以很容易地构建,而使用 useState 时,我们需要组装提交的数据。当表单增长时,它消除了引入新的状态变量的需求。处理多个表单时,您可能会发现在组件之间重复使用类似的状态变量,而 FormData 只需几行代码就可以轻松重用。

FormData 支持的一项功能是它会自动处理动态字段。即,如果您的表单具有动态生成的字段(根据用户输入添加/删除字段),使用 useState 管理它们的状态需要额外处理,而 FormData 会自动处理这些。

结束
希望你从这篇文章中学到了一些新东西。如果有任何疑问,请留下评论。谢谢!
用户评论