Когда виртуальный DOM имеет смысл?
Виртуальный DOM - одна из причин использования React. В предыдущем посте я продемонстрировал, что React ускоряет изменение существующих представлений и существенно не замедляет рендеринг новых представлений. В качестве образца представления я использовал страницу с таблицей, содержащей 40 000 ячеек. Ячейки таблицы примитивов отображали свои координаты в виде содержимого и заголовков. Отрисовка неоптимизированной таблицы заняла так много времени, что измеримые накладные расходы React, несомненно, были незначительными. В этом посте я хочу исследовать, может ли задержка рендеринга, вызванная React, быть ощутимой, если стили страницы оптимизированы для максимального сокращения времени рендеринга страницы.
Я повторю эксперимент, который описал ранее, но на этот раз я поэкспериментирую с более крупной таблицей, которая визуализируется в кратчайшие сроки, потому что она стилизована с производительностью рендеринга, улучшающей свойства CSS. Вы можете увидеть образец страницы по адресу https://reactvsnoreact.onrender.com/:
Код для отображения таблицы без React:
function row(vals) { const rowDiv = document.createElement("div"); rowDiv.className = 'row'; rowDiv.append(...vals.map(val => { const cell = document.createElement("div"); cell.title = val; cell.append(val); return cell; })); return rowDiv; } function table(data) { return data.map(vals => row(vals)); } export function render(data) { root.replaceChildren(...table(data)); }
По сути, функция render(data)
преобразует исходный массив 5000 * 20 строк в массив из 5000 div
, действующих как строки, каждая из которых содержит 20 div
ячеек. Я использовал сокращенные методы append()
и replaceChildren()
, чтобы немного упростить код. Узлы таблиц вставляются в div
с id
root
.
Эквивалентный непереносимый JSX-код имеет аналогичную структуру:
function Row({ row }) { return <div className="row"> {row.map((text, i) => <div key={i} title={text}>{text}</div>)} </div> } function Table({ rows }) { return rows.map((row, i) => <Row key={i} row={row}/>); } export function render(data) { ReactDOM.render(<Table rows={data} />, root); }
ReactDOM.render()
вставляет узлы таблиц в тот же div
с id
root
.
Во время измерений коды React-free и React-based используют одни и те же исходные данные для создания таблицы.
В моем эксперименте я измеряю общее время от вызова render(data)
до момента, когда браузер возобновляет выполнение JavaScript после того, как отрисовывает изменения в DOM:
export function execute(label, render) { return new Promise(resolve => { requestAnimationFrame(() => { const start = Date.now(); render(); setTimeout(() => { addResult(label, start, Date.now()); resolve(); }); }); }); }
requestAnimationFrame()
необходим для того, чтобы браузер немедленно отображал изменения в DOM. Чтобы еще лучше понять этот крошечный код, вы можете пройти тест на requestAnimationFrame()
.
Результаты сравнительного анализа
Задержка, вызванная React
Сначала я измеряю и сравниваю время, необходимое для отображения тестовой таблицы, используя показанный выше код React-free и React-based. Посмотрите на первые две строки таблицы результатов. Среднее время показывает, что React задерживает рендеринг нового дерева узлов на ~ (575–469) / 469 = 22%. В% разница кажется довольно значительной. Но с другой стороны, пользователи не будут очень благодарны, если страница появится на их экранах на 575–469 = 106 мс раньше. Я бы сказал, что React существенно не задерживает отрисовку структурированного содержимого с оптимальным стилем.
Более быстрый просмотр обновлений с помощью React
Виртуальная DOM становится полезной, когда код изменяет не всю DOM, а только несколько ее элементов. В следующей части эксперимента, после отображения таблицы, код отображает другую таблицу, которая отличается от ранее отображаемой таблицы в ячейке с координатами 10,20 . В одной таблице эта ячейка содержит ее координаты, в альтернативной таблице значение ячейки заменяется строкой TEST:
Две таблицы создаются с использованием двух разных массивов исходных данных. Альтернативный массив отличается от первого только одним элементом, содержащим TEST вместо 10,20.
Результаты отображаются в строках, начиная с третьей. Строки с именами noreact cell или react cell содержат время, затраченное на замену альтернативной таблицы первой таблицей. И наоборот, строки noreact cell2 и react cell2 содержат время, потраченное на замену первой таблицы альтернативной таблицей.
По результатам видно, что при отрисовке изменений в DOM браузер не выполняет никаких оптимизаций на основе предыдущего содержимого DOM. Если дочерние элементы заменяются идентичными или почти идентичными, браузер тратит на рисование столько же времени (460 и 439 мс), сколько он тратит на рисование тех же элементов, вставленных в пустой родительский элемент (469 мс).
Напротив, точечные изменения в DOM, вводимые посредством виртуальной DOM (108 и 103 мс), появляются на экране намного быстрее, чем вся таблица, вставленная в пустой контейнер (575 мс). Это ожидаемый результат, демонстрирующий назначение виртуальной DOM. Однако визуально разница между временем, необходимым для обновлений с React 108 мс, и временем, необходимым для перерисовки всей таблицы без React 469 мс, невелика.
Выводы
Неясно, может ли React быть полезен для страниц, отображающих структурированное содержимое с оптимальным стилем, такое как табличные данные. Хотя в цифрах виртуальная модель DOM, несомненно, полезна, улучшения в полностью приемлемом времени рендеринга не очень полезны. Преимуществ виртуального DOM может быть недостаточно, чтобы оправдать неудобства использования React. Лично я использую React как библиотеку для создания кода без ссылок. Иногда проще перестраивать все дерево документа в виртуальной модели DOM, чем сохранять ссылки на множество динамических элементов.
Образец кода можно загрузить с https://github.com/marianc000/reactVsNoReact, и вы можете поиграть с образцом страницы https://reactvsnoreact.onrender.com/