GitHub выкатил отличный пост о том, как они ускоряли рендеринг диффов в пулл-реквестах (исконно русские слова) — и внезапно выяснили, что браузеру становится плохо, когда в PR десятки тысяч строк.
Вот ссыль сразу: https://github.blog/engineering/architecture-optimization/the-uphill-climb-of-making-diff-lines-performant/
Главная проблема оказалась в том, что каждая строка diff-а была маленьким React-стартапом: 8–13 компонентов, куча DOM-нод и отдельные event handlers почти на всё подряд.
Старый подход выглядел примерно так:
<DiffLine>
<LineNumber />
<SyntaxHighlight>
<Token />
<Token />
</SyntaxHighlight>
</DiffLine>
И конечно же:
<div
onMouseEnter={...}
onMouseLeave={...}
onClick={...}
/>
Когда таких строк 10 000+, Chrome начинает потреблять память не в себя.
А дальше случилось прекрасное: GitHub героически переоткрыл event delegation — технику, которую jQuery нормально объяснял ещё лет 15 назад (не воспринимайте буквально, автоматическая делегация в реакте была и есть). Оказалось, что один обработчик событий на контейнер внезапно быстрее, чем 30 тысяч onMouseEnter на каждую строку. Кто бы мог подумать.
Новый вариант:
<table onMouseMove={handleHover}>
<tr data-line="42">
<td>const value = 1;</td>
</tr>
</table>
function handleHover(e) {
highlight(e.target.dataset.line)
}
В итоге GitHub выкинул 74% React-компонентов, почти вдвое снизил потребление памяти и ещё удалил пару лишних <code>-тегов из каждой строки, потому что 20 000 ненужных DOM-элементов — это всё ещё 20 000 ненужных DOM-элементов.
Мораль истории максимально простая: abstraction is not free. Иногда один обработчик событий и туповатый плоский код работают лучше, чем архитектура мечты из 400 reusable-компонентов, custom hooks и трёх уровней composition.
#github #react #virtualization
