React 18 — улучшение производительности

Недавно React выпустил версию 18 с некоторыми замечательными функциями.
В этом посте мы рассмотрим функции, связанные с производительностью.

useTransition

Это часть концепции параллелизма, где вы можете установить приоритет обновлений состояния.
Срочные обновления состояния могут быть приоритетны по сравнению с менее срочными (блокирующими) обновлениями.

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

Это наш пример. Он простой, у нас есть кнопка, которая открывает модальное окно, и внутри этого окна мы выводим список из 500 комментариев.
500 комментариев — это много, но это будет отлично работать на большинстве устройств.

import { useState } from "react";
import Comments from "../components/Comments";
import Modal from "../components/Modal";
import data from "../data/index.json";

export default function Home() {
  const [isOpen, setIsOpen] = useState(false);
  const [comments, setComments] = useState([]);

  return (
    <div className="p-4">
      <button
        className="px-4 py-2 border-none rounded-sm bg-blue-800 text-white"
        onClick={() => {
          setIsOpen(true);
          setComments(data);
        }}
      >
        Toggle modal
      </button>
      <Modal
        isOpen={isOpen}
        onClose={() => {
          setIsOpen(false);
          setComments([]);
        }}
      >
        <Comments comments={comments} />
      </Modal>
    </div>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Но если мы замедлим отрисовку компонента Comment 😈 все станет интереснее.
Для этого я добавил цикл for, который будет повторяться миллион раз.

const Comment = ({ name, email, body, className, onClick }: CommentProps) => {
  const soooSloww = [];

  for (let i = 0; i < 1000000; i++) {
    soooSloww.push(i);
  }

  return (
    <article className={className} onClick={onClick}>
      <h3 className="font-semibold">{name}</h3>
      <h4 className="text-gray-500 italic">{email}</h4>
      <p>{body}</p>
    </article>
  );
};
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, когда вы нажимаете на кнопку, чтобы открыть модальное окно, в течение нескольких секунд ничего не происходит.
Это происходит потому, что браузер занят отрисовкой медленных 500 компонентов Comment.
Через некоторое время модальное окно и комментарии отображаются.

С точки зрения пользователя, это очень плохой UX.
Как его улучшить?
Мы можем расставить приоритеты рендеринга, и в нашем примере важнее сначала отрисовать модальное окно, а затем комментарии.

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

const [pending, startTransition] = useTransition();
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь наш пример выглядит следующим образом

export default function Home() {
  const [isOpen, setIsOpen] = useState(false);
  const [comments, setComments] = useState([]);
  const [pending, startTransition] = useTransition();

  return (
    <div className="p-4">
      <button
        className="px-4 py-2 border-none rounded-sm bg-blue-800 text-white"
        onClick={() => {
          setIsOpen(true);
          startTransition(() => {
            setComments(data);
          });
        }}
      >
        Toggle modal
      </button>
      <Modal
        isOpen={isOpen}
        onClose={() => {
          setIsOpen(false);
          setComments([]);
        }}
      >
        {pending ? "Loading..." : <Comments comments={comments} />}
      </Modal>
    </div>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вы можете заметить, что при нажатии на кнопку мы обновляем состояние для показа модала, что является действием с более высоким приоритетом, и обновляем состояние комментариев в функции startTransition, которая сообщает React, что обновление состояния имеет более низкий приоритет.

Также мы использовали флаг pending, чтобы показать пользователю текст «Loading…» во время медленного рендеринга комментариев.
Теперь, после нажатия на кнопку, вы сразу же получите модальное окно, которое выглядит следующим образом:

Намного лучше для пользователя! 😀

useDeferredValue

Этот хук также сообщает React, что определенные обновления состояния имеют более низкий приоритет.
Он похож на useTransition и, честно говоря, я не уверен, в каких случаях вам следует предпочесть useDeferredValue, а не useTransition, если у вас есть идеи, пожалуйста, дайте мне знать в комментариях. 👇

Теперь наш предыдущий пример выглядит следующим образом и ведет себя аналогично, за исключением отсутствия флага pending.

export default function UseDeferredValues() {
  const [isOpen, setIsOpen] = useState(false);
  const [comments, setComments] = useState([]);
  const commentsToRender = useDeferredValue(comments);

  return (
    <div className="p-4">
      <button
        className="px-4 py-2 border-none rounded-sm bg-blue-800 text-white"
        onClick={() => {
          setIsOpen(true);
          setComments(data);
        }}
      >
        Toggle modal
      </button>
      <Modal
        isOpen={isOpen}
        onClose={() => {
          setIsOpen(false);
          setComments([]);
        }}
      >
        <Comments comments={commentsToRender} />
      </Modal>
    </div>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Автоматическое пакетирование

При работе с React вы должны стремиться к тому, чтобы повторных рендеров было как можно меньше.
Теперь React 18 помогает вам достичь этого с помощью автоматической пакетной обработки.

Предыдущие версии React выполняли пакетную обработку обновлений состояния только внутри обработчиков событий React, таких как onClick или onChange, чтобы избежать многократных повторных рендеров и повысить производительность.

Теперь React выполняет пакетное обновление состояния в обработчиках событий React, обещаниях, setTimeout, нативных обработчиках событий и так далее.

const AutomaticBatching = () => {
  const [countOne, setCountOne] = useState(0);
  const [countTwo, setCountTwo] = useState(0);

  console.log("render");

  const onClick = useCallback(() => {
    setCountOne(countOne + 1);
    setCountTwo(countTwo + 1);
  }, [countOne, countTwo]);

  useEffect(() => {
    document.getElementById("native-event").addEventListener("click", onClick);

    return () =>
      document
        .getElementById("native-event")
        .removeEventListener("click", onClick);
  }, [onClick]);

  const onClickAsync = () => {
    fetch("https://jsonplaceholder.typicode.com/todos/1").then(() => {
      setCountOne(countOne + 1);
      setCountTwo(countTwo + 1);
    });
  };

  const onClickTimeout = () =>
    setTimeout(() => {
      setCountOne(countOne + 1);
      setCountTwo(countTwo + 1);
    }, 200);

  return (
    <div className="p-4">
      <ul className="mb-8">
        <li>Count one: {countOne}</li>
        <li>Count two: {countTwo}</li>
      </ul>
      <Button onClick={onClick}>Batching in click event</Button>
      <Button id="native-event" className="ml-4">
        Batching in native click event
      </Button>
      <Button className="ml-4" onClick={onClickAsync}>
        Batching in fetch
      </Button>
      <Button className="ml-4" onClick={onClickTimeout}>
        Batching in timeout
      </Button>
    </div>
  );
};
Вход в полноэкранный режим Выход из полноэкранного режима

В этом примере видно, что в каждом обработчике событий мы имеем два изменения состояния, но только один рендер. Вы можете заметить, что для каждого события используется один console.log.

Улучшенный саспенс

Suspense работает с React.lazy таким образом, что приостанавливает рендеринг компонента до его загрузки и в это время рендерит fallback.

const LazyComponent = lazy(() => import("../components/LazyComponent"));

<Suspense fallback={<div>Loading...</div>}>
  <LazyComponent />
</Suspense>
Вход в полноэкранный режим Выход из полноэкранного режима

Это отличный способ повысить производительность, поскольку таким образом вы не будете включать в начальный пакет некоторые части приложения, которые вам не нужны немедленно (например, модалы).

Но, Suspense — это не новая функция, она существовала и в предыдущих версиях React, новое в том, что теперь она работает с рендерингом на стороне сервера, чего раньше не было.

На этом все, друзья, надеюсь, вам понравится новая версия React. 😀
Все примеры из вышеперечисленных вы можете найти здесь.

Оцените статью
Procodings.ru
Добавить комментарий