Статья опубликована в рамках: Научного журнала «Студенческий» № 18(356)
Рубрика журнала: Информационные технологии
ОПТИМИЗАЦИЯ РЕНДЕРИНГА В ВЕБ-ПРИЛОЖЕНИЯХ: ФОРМАЛЬНАЯ МОДЕЛЬ ПРОИЗВОДИТЕЛЬНОСТИ РЕАКТИВНЫХ ПОЛЬЗОВАТЕЛЬСКИХ ИНТЕРФЕЙСОВ БЕЗ VIRTUAL DOM
Современные веб-приложения предъявляют высокие требования к производительности пользовательского интерфейса (UI). Объектная модель документа (DOM) является стандартным программным интерфейсом для взаимодействия с HTML-документами в браузере. Структура DOM представляет собой дерево, в корне которого находится объект Document, а узлами — элементы HTML-документа.

Рисунок 1. Структура DOM-дерева веб-документа
Операции изменения DOM (вставка, удаление, обновление узлов) ресурсоёмки: каждая мутация может вызывать перерасчёт стилей, перекомпоновку (reflow) и перерисовку (repaint) страницы. Для решения проблемы частых обновлений DOM в React был предложен подход Virtual DOM — легковесное объектное представление дерева компонентов в памяти [2]. При каждом изменении состояния React создаёт новый Virtual DOM, сравнивает его с предыдущим (reconciliation) и применяет минимальный набор мутаций к реальному DOM. Данный подход имеет фундаментальное ограничение: алгоритм reconciliation обходит всё виртуальное поддерево при каждом обновлении, даже если изменился единственный узел. Как указывается в [4], для больших приложений «многочисленные изменения будут приводить к многократным пересозданиям отдельных поддеревьев и их проверкам с предыдущей версией».
Альтернативный подход fine-grained reactivity реализован в SolidJS [3] и Svelte [7]. В данной модели компилятор во время сборки статически анализирует зависимости между данными (сигналами) и DOM-элементами, создавая прямые привязки. При изменении сигнала обновляются только подписанные на него DOM-узлы, без обхода остального дерева [3]. Цель работы — построение формальной модели стоимости обновления UI для обоих подходов и её верификация экспериментальными данными.
1. Теоретическая модель
Подход Virtual DOM основан на поддержании облегчённого представления DOM-дерева. При вызове setState запускается алгоритм reconciliation, рекурсивно сравнивающий каждый узел нового дерева со старым [2]. Суммарная стоимость I обновлений вычисляется по формуле:

где
— размер виртуального поддерева при i-м обновлении. Для однородного списка из n элементов сложность одного обновления составляет O(n).
В SolidJS компилятор создаёт граф зависимостей, связывающий каждый сигнал с конкретными DOM-узлами [3]. При вызове setSignal(v) уведомляются только k прямых подписчиков:

Для типичного случая (
) сложность составляет O(k), где
. Теоретическое ускорение в этом случае может быть вычислено по формуле:

При n = 1000 и k = 1 теоретическое ускорение рассчитывается как произведение 1000 на число посещённых узлов. При пакетном обновлении всех n элементов (
) оба подхода выполняют O(n) работы, и разница определяется лишь константными факторами.
2. Методика эксперимента
Для верификации модели разработаны два приложения на React 19.2.5 и SolidJS 1.9.12, собираемых Vite 8. Среда выполнения: Node.js v22, macOS, Apple M-серии; браузерные тесты — Chromium (headless, Playwright).
Таблица 1.
Сравнительная характеристика экспериментальной среды и используемых технологий
|
Параметр |
React |
SolidJS |
|---|---|---|
|
Фреймворк |
React 19.2.5 |
SolidJS 1.9.12 |
|
Роутер |
react-router-dom 7.14.1 |
@solidjs/router 0.16.1 |
|
Сборщик |
Vite 8 |
Vite 8 |
Проведены четыре серии экспериментов:
1) оценка размера production-бандла (npm run build);
2) алгоритмическая симуляция в Node.js: 1000 одиночных обновлений при N от 100 до 10 000, 10 прогонов после 3 warmup;
3) браузерный бенчмарк точечных обновлений: 200 случайных обновлений одной строки из N, MutationObserver, mean ± σ по 10 прогонам;
4) бенчмарк пакетного обновления всех N элементов.
3. Результаты экспериментов
Production-сборка (эксперимент 1) выявила существенное различие в размере бандла:
Таблица 2.
Сравнение размеров production-бандлов React и SolidJS
|
Метрика |
React |
SolidJS |
Разница |
|---|---|---|---|
|
JS (gzip) |
686,09 kB |
18,96 kB |
36,2X |
|
CSS (gzip) |
1,59 kB |
1,47 kB |
≈ 1X |
|
Всего dist/ |
2,1 MB |
84 KB |
25,6X |
Вклад чистого runtime: react + react-dom — ~42 kB gzip, solid-js — ~7 kB gzip (разница 6X). SolidJS-компилятор генерирует прямые DOM-инструкции во время сборки, устраняя необходимость в runtime-движке reconciliation [7]. Данные согласуются с js-framework-benchmark [5]: solid-js — 4,9 kB (brotli) против 16,4 kB у React hooks.
Алгоритмическая симуляция (эксперимент 2) подтвердила теоретическую формулу:
Таблица 3.
Результаты алгоритмической симуляции точечных обновлений UI
|
N |
React (мс) |
SolidJS (мс) |
Ratio |
Посещений React |
Посещений SolidJS |
|---|---|---|---|---|---|
|
100 |
0,522 |
0,166 |
3,1X |
100 000 |
1 000 |
|
1 000 |
2,127 |
0,232 |
9,2X |
1 000 000 |
1 000 |
|
5 000 |
10,669 |
1,224 |
8,7X |
5 000 000 |
1 000 |
|
10 000 |
19,503 |
2,138 |
9,1X |
10 000 000 |
1 000 |
При N = 10 000 SolidJS выполняет в 10 000 раз меньше операций сравнения на одно обновление, что точно соответствует формулам
и
при
.
Браузерный бенчмарк точечных обновлений (эксперимент 3):
Таблица 4.
Производительность точечных DOM-обновлений в браузере
|
N |
React (мс) |
σ |
SolidJS (мс) |
σ |
Ratio |
|---|---|---|---|---|---|
|
100 |
33,29 |
0,28 |
32,84 |
0,15 |
1,01X |
|
500 |
51,60 |
5,06 |
33,27 |
0,31 |
1,55X |
|
1 000 |
83,23 |
0,35 |
33,06 |
0,33 |
2,52X |
|
5 000 |
348,17 |
24,06 |
33,55 |
0,36 |
10,38X |
Время обновления React растёт линейно с N (от 33 мс при N = 100 до 348 мс при N = 5000), тогда как время SolidJS остаётся константным (~33 мс). Число DOM-мутаций одинаково (200) — оба фреймворка применяют ровно необходимое количество изменений.
Профилирование памяти (JS heap delta): при N = 500–1000 React аллоцирует ~2,7–3,5 MB на VDOM-объекты, тогда как SolidJS стабильно потребляет ~110 KB вне зависимости от N (разница до 31,8X при N = 1000). При N = 100 overhead инициализации графа зависимостей SolidJS перевешивает стоимость VDOM-структур; граница пересечения — между N = 100 и N = 500.
Бенчмарк пакетного обновления (эксперимент 4): при обновлении всех N элементов разница составляет ≤ 1 % (React: 33,27–33,73 мс, SolidJS: 33,28–33,59 мс), подтверждая теоретическую границу паритета — оба подхода выполняют O(n) работы.
4. Анализ результатов
Ключевое различие между React и SolidJS состоит в стадии определения зависимостей. React реализует runtime-реактивность: при каждом изменении состояния выполняется reconciliation — рекурсивный обход виртуального дерева [2]. Данный подход универсален, однако вносит overhead, пропорциональный размеру дерева.
SolidJS реализует compile-time реактивность: компилятор анализирует JSX-шаблоны, определяет зависимости DOM-узлов от сигналов и генерирует инструкции прямого обновления [7]. Как описано в [3], в fine-grained reactive системе «updates are made to the targeted attribute that needs to be changed, avoiding broader and, sometimes unnecessary, updates». Данный подход соответствует принципу инкрементальных вычислений: старый результат обновляется инкрементально вместо повторного выполнения всего вычисления [4].
Независимый бенчмарк js-framework-benchmark [5] стабильно демонстрирует лидерство signal-based фреймворков (SolidJS, Svelte) над VDOM-фреймворками в задачах точечного обновления и потребления памяти. TC39 proposal «Signals» (Stage 1) [6] формализует примитивы Signal.State и Signal.Computed как стандартные конструкции JavaScript, что свидетельствует о признании signal-based модели на уровне стандарта языка. Интерпретация результатов требует учёта ограничений: тестировались конкретные версии фреймворков; бенчмарк использует синтетическую таблицу; профилирование памяти подвержено влиянию GC при больших N.
В настоящей работе предложена и экспериментально верифицирована формальная модель стоимости обновления UI для двух парадигм: Virtual DOM (React) и fine-grained reactivity (SolidJS). Математическую модель можно представить следующими формулами:

где n — размер виртуального поддерева, k — число подписчиков сигнала,
.
Экспериментальная верификация подтвердила следующие положения: при точечных обновлениях SolidJS превосходит React до 10,4X при N = 5000; при пакетных обновлениях оба демонстрируют паритет (≤ 1 %); SolidJS потребляет в 31,8X меньше памяти при N = 1000. Преимущество fine-grained reactivity определяется compile-time графом зависимостей — runtime-dispatcher уведомляет только прямых подписчиков без обхода дерева [3, 7] — и отсутствием VDOM overhead: нет создания виртуальных объектов, двухфазного коммита и scheduler overhead [1]. Полученные результаты согласуются с js-framework-benchmark [5] и подтверждают индустриальный тренд к отказу от Virtual DOM, формализуемый через TC39 proposal Signals [6].
Список литературы:
- Почему реактивность без VDOM (с реальным DOM) лучше, чем реактивность с VDOM? // Habr. – б. г. – URL: https://habr.com/en/articles/803779/ (дата обращения: 15.04.2026).
- React и альтернативы // Дока. – б. г. – URL: https://doka.guide/tools/react-and-alternatives/ (дата обращения: 15.04.2026).
- Fine-grained reactivity // SolidJS Documentation. – б. г. – URL: https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity (дата обращения: 15.04.2026).
- Freeman P. Incrementally Improving The DOM // Blog.functorial.com. – 2018. – URL: https://blog.functorial.com/posts/2018-04-08-Incrementally-Improving-The-DOM.html (дата обращения: 15.04.2026).
- Krause S. js-framework-benchmark // GitHub. – б. г. – URL: https://github.com/krausest/js-framework-benchmark (дата обращения: 15.04.2026).
- Ehrenberg D. TC39 Proposal: JavaScript Signals Standard (Stage 1) / D. Ehrenberg, R. Eisenberg, Y. Katz [и др.]. – 2024. – URL: https://github.com/tc39/proposal-signals (дата обращения: 15.04.2026).
- Understanding Compile-Time Reactivity in SolidJS and Svelte // Leapcell Blog. – б. г. – URL: https://leapcell.io/blog/understanding-compile-time-reactivity-in-solidjs-and-svelte (дата обращения: 15.04.2026).

