Что такое Батчинг в ReactJS

месяц назад·4 мин. на чтение

В данной статье мы рассмотрим понятие "батчинг" в ReactJS и посмотрим, как он может повысить производительность и улучшить пользовательский опыт веб-приложений.

React 18 включает в себя улучшения производительности «из коробки» путем использования более частых операций пакетной обработки по умолчанию, что устраняет необходимость вручную объединять обновления в коде приложения или библиотеки. Это функциональность, о которой большинству пользователей можно не думать. Однако она может быть актуальна для преподавателей и разработчиков библиотек.

Что такое пакетная обработка (batching)?

Пакетная обработка (batching) - это когда React группирует несколько обновлений состояния в одно перерисовку для повышения производительности. Например, если у вас есть два обновления состояния внутри одного события клика, React всегда собирает их в одну перерисовку. Если вы выполните следующий код, вы увидите, что каждый раз, когда вы кликаете, React выполняет только одну перерисовку, хотя вы устанавливаете состояние дважды:
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    setCount(c => c + 1); // Пока не происходит повторное отображение
    setFlag(f => !f); // Пока не происходит повторное отображение
    // React перерисует только один раз в конце (это пакетная обработка!)
  }

  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}
Это отлично для производительности, потому что это позволяет избежать ненужных повторных рендеров. Это также предотвращает отображение компонента в "незаконченном" состоянии, где была обновлена только одна переменная состояния, что может вызвать ошибки. Это может напомнить вам о том, как официант ресторана не бежит на кухню, когда вы выбираете первое блюдо, а ждет, пока вы закончите заказ. Однако, React не всегда одинаково объединял обновления. Например, если вам нужно загрузить данные, а затем обновить состояние в указанном выше handleClick, то React не будет объединять обновления и будет выполнять два независимых обновления. Это происходит потому, что React раньше выполнял пакетную обработку только во время события браузера (например, нажатия кнопки), а здесь мы обновляем состояние после обработки события (в callback-функции fetch):
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    fetchSomething().then(() => {
      // React 17 и более ранние НЕ объединяют эти обновления, потому что
      // они запускаются ПОСЛЕ события в callback-функции, а не ВО ВРЕМЯ события
      setCount(c => c + 1); // Вызывает повторное отображение
      setFlag(f => !f); // Вызывает повторный рендеринг
    });
  }

  return (
   <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}
До React 18 мы объединяли обновления только в обработчиках событий React. Обновления внутри Promise, setTimeout, нативных обработчиков событий или любых других событий по умолчанию не объединялись в React.

Что такое автоматическая пакетная обработка?

Начиная с React 18 с использованием createRoot, все обновления будут автоматически объединяться, независимо от их происхождения. Это означает, что обновления внутри таймеров, Promise, нативных обработчиков событий или любых других событий будут объединяться так же, как и обновления внутри событий React. Мы ожидаем, что это приведет к уменьшению работы по отрисовке и, следовательно, к лучшей производительности в ваших приложениях:
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    fetchSomething().then(() => {
      // React 18 и позднее ОБЪЕДИНЯЕТ эти обновления:
      setCount(c => c + 1);
      setFlag(f => !f);
      // React перерисует только один раз в конце (это пакетная обработка!)
    });
  }

  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}
Примечание: Ожидается, что вы начнете использовать createRoot вместе с переходом на React 18. React будет автоматически объединять обновления, независимо от того, где они происходят, так что это:
function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React перерисует только один раз в конце (это пакетная обработка!)
}
и равносильно этому:

setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React перерисует только один раз в конце (это пакетная обработка!)
}, 1000);
и равносильно этому:

fetch(/*...*/).then(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React перерисует только один раз в конце (это пакетная обработка!)
})
и равносильно этому:

elm.addEventListener('click', () => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React перерисует только один раз в конце (это пакетная обработка!)
});
Примечание: React объединяет обновления только тогда, когда это обычно безопасно. Например, React гарантирует, что для каждого события, инициированного пользователем, такого как клик или нажатие клавиши, DOM полностью обновляется перед следующим событием. Это гарантирует, например, что форма, которая отключается при отправке, не может быть отправлена дважды.

Что, если я не хочу использовать пакетную обработку?

Обычно пакетная обработка безопасна, но некоторый код может зависеть от считывания чего-либо из DOM сразу после изменения состояния. Для таких случаев вы можете использовать ReactDOM.flushSync(), чтобы отключить пакетную обработку:
import { flushSync } from 'react-dom'; // Обратите внимание: react-dom, а не react

function handleClick() {
  flushSync(() => {
    setCounter(c => c + 1);
  });
  // React уже обновил DOM
  flushSync(() => {
    setFlag(f => !f);
  });
  // React уже обновил DOM
}
Подробнее в оригинальной статье на GitHub

Обработка форм в React с помощью хуков

2 года назад·3 мин. на чтение

В этой статье рассмотрим, как обрабатывать формы в приложениях react с помощью хуков.

Формы

Формы позволяют нам принимать данные от пользователей и отправлять их на сервер для обработки. Это различные типы форм, такие как «Вход», «Регистрация» и т.д. В HTML5 есть элементы формы, такие как input, textarea, select, они поддерживают собственное внутреннее состояние в DOM, но в React мы поддерживаем состояние элементов формы внутри компонента, чтобы мы могли иметь полный контроль над элементами формы.

Что такое обработка форм

Обработка форм означает, как мы обрабатываем данные формы, когда пользователь изменяет значение или отправляет форму. Давайте посмотрим на пример того, как мы обрабатываем данные инпута с помощью хуков React.
import React,{useState} from 'react';

function Form() {
  const [name,setName] = useState('');

  function handleNameChange(e){
    setName(e.target.value)
  }

  function handleSubmit(e) {
    e.preventDefault() // останавливаем перезагрузку страницы по умолчанию
    console.log(name);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input placeholder="Name" value={name}
        onChange={handleNameChange}/>
      <button>Submit</button>
    </form>
  )
}
В приведенном выше коде мы установили для атрибута value элемента input свойство name, а метод обработчика событий onChange - handleNameChange - запускается каждый раз, когда мы вводим некоторые данные в элемент input, и он обновляет свойство name с помощью метода setName, так что мы продолжаем синхронизировать значение с состоянием react. handleSubmit используется для отправки формы.

Элемент select

<select> помогает нам создать выпадающий список. Давайте посмотрим на пример того, как создать выпадающий список и обрабатывать данные.
import React,{useState} from 'react';

function FormSelect() {
  // начальное значение состояния - строка 'react'
  const [framework, setFramework] = useState('react');

  function handleChange(e){
    setFramework(e.target.value);
  };

  function handleSubmit(e){
    e.preventDefault();
    console.log(framework);
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Choose your framework</h2>
      <select onChange={handleChange} value={framework}>
        <option value="react">React</option>
        <option value="angular">Angular</option>
        <option value="vue">Vue</option>
      </select>
      <button type="submit">Submit</button>
    </form>
  );
}
Здесь мы создали выпадающий список фреймворков в элементе <select> мы установили атрибут value для свойства framework и добавили обработчик событий onChange. Вложенные элементы <option> содержат атрибут value, который содержит данные, так что всякий раз, когда мы выбираем конкретный параметр, вызывается метод handleChange и изменяет значение свойства framework значением атрибута <option>. Вы видели, что элемент select также следует аналогичному шаблону, как и элемент input, так почему бы нам не создать пользовательский хук и повторно использовать его в каждом элементе формы?

Создание пользовательских хуков для обработки формы

import React, {useState} from 'react';

function useInput(initialValue) {
  const [value,setValue] = useState(initialValue);

  function handleChange(e) {
    setValue(e.target.value);
  }

  return [value,handleChange];
}
Здесь мы создали пользовательский хук под названием useInput, давайте его применим.

Использование пользовательского хука useInput()

function LoginForm(){

  const [email, setEmail] = useInput('');
  const [password, setPassword] = useInput('');

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

  return (
    <form onSubmit={handleSubmit}>
      <input placeholder="Email" type="email"
        value={email} onChange={setEmail}/>
      <input placeholder="password" type="password"
        value={password} onChange={setPassword}/>
      <button>Submit</button>
    </form>
  )
}
Теперь наш компонент LoginForm выглядит намного чище с помощью пользовательского хука useInput(). Точно так же мы можем использовать наш хук useInput с другими элементами формы.

Пример с Radio button

function RadioButtons() {
  const [data] = useState({male:"male",female:"female",other:"other"})
  const [gender, setGender] = useInput("");
  function handleSubmit(e) {
    e.preventDefault();
    console.log(gender);
  }

  return (
    <form onSubmit={handleSubmit}>
      <h1>Select Your Gender</h1>
      <div>
        <input type="radio" id={data.male} value={data.male}
          checked={data.male === gender} onChange={setGender}/>
        <label htmlFor={data.male}>Male</label>
      </div>
      <div>
        <input type="radio" id={data.female} value={data.female}
          checked={data.female === gender} onChange={setGender}/>
        <label htmlFor={data.female}>Female</label>
       </div>
       <div>
         <input type="radio" id={data.other}  value={data.other}
           checked={data.other === gender} onChange={setGender}/>
         <label htmlFor={data.other}>Other</label>
       </div>
     <button>submit</button>
    </form>
  )
}

Пример элемента Textarea

function Comments() {
  const [comment, setComment] = useInput("");
  function handleSubmit(e) {
    e.preventDefault();
    console.log(comment);
  }

  return(
    <form onSubmit={handleSubmit}>
      <textarea value={comment} onChange={setComment}/>
      <button>submit</button>
    </form>
  )
}