React vs. Vanilla: Сколько ресурсов потребляет один клик?
React, как и любой другой JavaScript-фреймворк, выполняет множество скрытых задач, о которых мы, разработчики, не задумываемся. Это нормальная практика: мы фокусируемся на решении проблем, и чем проще реализация, тем лучше. Однако важно понимать, что даже если мы не всегда вникаем в детали работы фреймворка, он все равно оказывает влияние на производительность приложения.
JavaScript по-прежнему остается ведущим языком веб-разработки, и вряд ли в ближайшем будущем уступит свои позиции. Более того, многие нативные приложения (iOS, Android, Smart TV) используют гибридные решения, сочетающие нативную разработку с веб-технологиями.
В этой статье мы сравним производительность простого счетчика, реализованного на React, с его ванильной JavaScript-версией.
Инструменты
Для измерения производительности мы будем использовать вкладку Chrome DevTools "Performance". Она позволяет записывать и анализировать работу веб-приложения, предоставляя информацию по следующим ключевым показателям:
- JS Heap: Область памяти, где хранятся объекты, массивы и функции в JavaScript.
- DOM Nodes: Отдельные элементы, атрибуты или текст в HTML-коде, представленные в DOM.
- Прослушиватели событий: Функции, реагирующие на определенные события, такие как щелчки мыши или нажатия клавиш.
Демонстрация создание простого счетчика React
В качестве примера возьмем простой счетчик. Он будет состоять из кнопки с обработчиком клика, который увеличивает значение счетчика при каждом нажатии.
Сравнение:
Далее мы сравним результаты работы счетчика на React и Ванильном JavaScript, анализируя изменения в heap JS, количестве узлов DOM и прослушивателей событий при каждом клике.
Код React выглядит следующим образом:
"use client";
import { useState } from "react";
export default function Page() {
const [counter, setCounter] = useState(0);
const incrementClickHandler = (event: { preventDefault: () => void }) => {
event.preventDefault();
setCounter((prevCounter) => prevCounter + 1);
};
return (
<div style={{ maxWidth: 800, margin: "0 auto" }}>
<a
href="#"
style={{
display: "inline-block",
padding: "20px 40px",
fontSize: 28,
border: "1px solid black",
width: "100%",
textAlign: "center",
marginTop: 40,
}}
onClick={incrementClickHandler}
>
Increment {counter}
</a>
</div>
);
}
Код довольно понятен. Следует отметить, что демонстрация выполняется поверх Next.js, поэтому нам нужен use client
. В остальном это всего лишь базовый компонент React.
Пользовательский интерфейс React Counter
20 секунд и всего один щелчок мышью
Теперь я собираюсь открыть вкладку "Производительность" в Chrome, щелкнуть значок записи и дать ей поработать в течение 20 секунд, нажав на кнопку только один раз. По истечении 20 секунд результаты работы будут выглядеть следующим образом:
Посмотрите, как всего одним щелчком мыши цифры увеличиваются до:
React | |
JS Heap | 3.4MB |
Nodes | 47 |
Listeners | 287 |
20 секунд с одним щелчком в секунду
Теперь я собираюсь дать ему поработать еще 20 секунд, но на этот раз я буду нажимать на кнопку раз в секунду. Давайте посмотрим на результаты:
React | |
JS Heap | 4MB |
Nodes | 46 |
Listeners | 331 |
В React следует отметить две вещи:
- При обновлении переменной состояния компонент отрисовывается повторно, что означает, что в данном случае компонент отрисовывался 20 раз.
- Благодаря виртуальной DOM, React обновляет только те узлы, которые необходимо обновить.
Теперь давайте вернемся к графику и посмотрим, как увеличиваются синяя линия (Heap JS) и желтая линия (слушатели), в то время как зеленая линия (узлы) остается неизменной.
Также стоит отметить, что цифры не сильно изменились по сравнению с запуском в один клик.
Демонстрация: Создание счетчика Vanilla JavaScript
Теперь у нас тот же пользовательский интерфейс, но на этот раз он построен с использованием простых HTML и JavaScript — без использования фреймворков.
<html>
<head>
<script>
let increment = 0;
window.onload = function () {
document.querySelector("#counter").innerText = increment;
document.querySelector("a").addEventListener("click", function (event) {
event.preventDefault();
increment++;
document.querySelector("#counter").innerText = increment;
});
};
</script>
</head>
<body style="max-width: 800; margin: 0 auto; font-family: monospace;">
<a
href="#"
style="
display: inline-block;
padding: 20px 40px;
font-size: 28px;
border: 1px solid black;
width: 100%;
text-align: center;
text-decoration: none;
color: black;
margin-top: 40;
box-sizing: border-box;
"
>Increment <span id="counter"></span>
</a>
</body>
</html>
Следует упомянуть необходимость следующего элемента:
<span id="counter"></span>
который обрабатывается с помощью JavaScript для обновления его содержимого:
document.querySelector("#counter").innerText = increment;
Интерфейс счетчика Vanilla
20 секунд и всего один клик
Я снова щелкну по значку записи и оставлю запись на 20 секунд, нажав на кнопку только один раз.
Взгляните на результаты:
Vanilla | |
JS Heap | 1.7MB |
Nodes | 16 |
Listeners | 20 |
20 секунд, по одному щелчку в секунду
Я снова собираюсь щелкнуть значок записи и дать ей поработать еще 20 секунд, но на этот раз я буду нажимать на кнопку раз в секунду. Ознакомьтесь с результатами:
Vanilla | |
JS Heap | 2.3MB |
Nodes | 42 |
Listeners | 77 |
Как и в примере с React, потребление памяти (синяя линия) и количество слушателей событий (желтая линия) постепенно увеличивались. Количество узлов DOM (зеленая линия) оставалось стабильным, но каждый клик вызывал кратковременный рост, после чего оно возвращалось к исходному значению.
Сборка мусора: невидимый помощник
Сборка мусора — это процесс автоматического освобождения ресурсов, которые больше не используются. JavaScript выполняет сборку мусора автоматически, нам не нужно вмешиваться. В наших примерах мы видели, как ресурсы потребляются, но в определенный момент JavaScript сам высвобождает некоторые из них с помощью сборщика мусора.
Выводы
Независимо от количества кликов (1 или 20), потребление ресурсов незначительно отличается. JavaScript выделяет ресурсы при первом клике, и последующие клики лишь увеличивают это потребление. Однако скачок между нулевым и первым кликом более заметен, чем между последующими.
Ниже представлены конечные значения потребления ресурсов для 20 кликов в обеих версиях:
Vanilla | React | |
JS Heap | 2.3MB | 4.0MB |
Nodes | 42 | 46 |
Listeners | 77 | 331 |
Понятно, что React потребляет больше ресурсов, так как это неизбежная плата за использование фреймворка.
Ключевое различие заключается в том, что React добавляет все узлы DOM сразу, в то время как ванильная версия добавляет их по мере кликов. Тем не менее, в конечном итоге обе версии имеют примерно одинаковое количество узлов DOM.
Демонстрация достаточно проста, и на этом уровне производительность практически не отличается. Как уже упоминалось ранее, использование фреймворка имеет свою цену, но оно оправдано, учитывая все преимущества и удобства, которые он предлагает.