Узнайте, как оптимизировать производительность форм реагирования, заменив useState встроенной функцией JavaScript.

Введение

Когда дело доходит до обработки форм в реакции, наиболее популярным подходом является сохранение входных значений в переменных состояния. Одна из причин следовать этому подходу заключается в том, что, в конце концов, это React, и все склонны использовать связанные с ним хуки. Использование хуков решает множество проблем в React, но действительно ли это необходимо, когда дело касается форм? Давайте проверим это.

Проблема с использованием состояний

Как мы уже знаем, всякий раз, когда значение переменной состояния изменяется внутри компонента, реакция будет повторно отображать компонент, чтобы он соответствовал его текущему состоянию. Хотя это не является большой проблемой для небольших приложений, это может привести к снижению производительности по мере увеличения размера вашего приложения.

Когда дело доходит до формы, реакция будет пытаться повторно отобразить компонент каждый раз, когда изменяется ввод (состояние).

Боковой совет: я наткнулся на этот ответ на StackOverflow, который очень полезен для подсчета количества рендерингов компонента. Мы также будем использовать эту служебную функцию в нашем коде.

Давайте реализуем и посмотрим на проблему с состояниями в действии.

Создайте базовое приложение реагирования, используя vite, и очистите ненужные файлы после создания проекта.

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

Давайте создадим компонент реагирования (скажем, FormWithState), содержащий форму, которая принимает два входных параметра email и password. Мы будем использовать useState для управления вводом формы.

import { useEffect, useRef, useState } from "react";

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 для обработки форм

Итак, альтернативный подход — использовать собственный интерфейс FormData JavaScript.

Есть три способа создать новый объект FormData, как описано в официальной документации.

new FormData()
new FormData(form)
new FormData(form, submitter)

Мы будем использовать второй метод, потому что форма у нас уже есть. Нам просто нужно передать элемент формы конструктору, и он автоматически заполнит значения формы. Чтобы это работало, нам также нужно добавить атрибут name к тегу input. Давайте проверим этот подход. Создайте компонент (скажем, FormWithoutState).

import { useEffect, useRef } from "react";

export default function FormWithoutState() {
  // 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 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. Вместо этого мы добавляем атрибут name к тегу input. Как только пользователь отправляет форму, в функции handleSubmit мы создаем FormData, предоставляя объект формы через e.currentTarget. Затем мы повторяем метод FormData.entries(), чтобы получить ключ и значение формы для построения тела формы. Затем мы можем использовать этот объект для дальнейшей проверки ввода и отправки через API fetch или axios. Но как насчет влияния повторного рендеринга компонентов при таком подходе? Давайте проверим это. Добавьте этот компонент в компонент App и откройте http://localhost:5173.

Вы не удивлены? Компонент вообще не перерисовывался.

Преимущества использования FormData

  1. Форма входные значения фиксируются автоматически без необходимости поддерживать переменную состояния для каждого поля ввода.
  2. Компонент не выполняет повторную визуализацию при вводе пользователя.
  3. Тело запроса API можно легко создать при использовании FormData, тогда как при использовании useState нам потребуется собрать данные для отправки.
  4. Это устраняет необходимость введения новых переменных состояния по мере роста формы.
  5. Имея дело с несколькими формами, вы можете в конечном итоге дублировать одинаковые переменные состояния в разных компонентах, тогда как FormData можно легко использовать повторно с помощью всего лишь нескольких строк кода.
  6. Одна вещь, которую FormData поддерживает «из коробки», — это автоматическая обработка динамических полей. т. е. если ваша форма имеет динамически генерируемые поля (добавление/удаление полей на основе ввода пользователя), управление их состоянием с помощью useState требует дополнительной обработки, тогда как FormData позаботится об этом автоматически.

Заключение

Вы можете проверить код этой статьи в песочнице кода здесь. Надеюсь, вы узнали что-то новое из этой статьи. Оставьте комментарий, если у вас есть сомнения. Спасибо за прочтение.

На простом английском языке

Спасибо, что вы являетесь частью нашего сообщества! Прежде чем уйти: