разбор продуктаобновлено 2026-08-20

Центр событий: точечные обновления без лишних перерисовок

Live-лента получает больше событий, чем пользователю нужно видеть как отдельные кадры. Разбираем нормализованный store, подписки по entity id и коалесцирование обновлений, чтобы интерфейс оставался быстрым.

Иллюстрация к материалу: Live-center во frontend: точечные обновления без лишних перерисовок
skidrd / Wikimedia CommonsCC BY 2.0

Поток данных быстрее глаза пользователя

Live-center может получать больше изменений, чем имеет смысл рисовать отдельными commit. Клиенту нужна правильная последняя версия, но не обязательно отдельный render для каждого промежуточного тика.

Нормализованное хранилище и точечные подписки

События, рынки и selections хранятся по id. Карточка подписывается только на сущности, которые отображает. Stream handler меняет store, а UI scheduler коалесцирует обновления до ближайшего кадра. Это снижает площадь перерисовки.

Мемоизация не лечит неправильную модель

Если каждый delta создаёт новый огромный объект страницы, memo-компоненты всё равно будут получать изменившиеся ссылки. Сначала нормализуется состояние и гранулярность подписки, потом добавляется memoization.

Видимость как сигнал приоритета

Карточки за пределами viewport могут получать store updates, но их визуальный commit можно отложить. При возвращении в область видимости компонент читает текущий snapshot, а не проигрывает очередь старых анимаций.

Неправильный ключ создаёт «чужую цену»

При переиспользовании элемента списка React/Vue key должен идентифицировать сущность, а не позицию. Иначе локальные transitions и подписки могут остаться от предыдущего рынка. Это особенно заметно при сортировке live-листа.

Где обычно теряется производительность

Проблема live-center редко начинается с одного тяжёлого компонента. Чаще каждое обновление затрагивает слишком большой участок дерева: меняется общий объект event, пересчитываются коллекции, пересоздаются props и десятки карточек получают новый render даже без визуального изменения.

Нормализованный store и подписки по entity id уменьшают этот радиус. Дополнительно стоит отделить скорость входящего потока от частоты paint: несколько дельт за один кадр можно свести к последнему актуальному состоянию. Пользователь увидит свежий результат, а браузер не будет отрисовывать промежуточные версии, которые всё равно невозможно различить.

  • Профилировать не только render time, но и количество затронутых компонентов на одно обновление.
  • Приоритизировать карточки в viewport и снижать частоту для невидимых секций.
  • Не использовать индекс массива как key в списке событий, которые меняют порядок.

Бюджет рендера задаётся от видимого экрана

Если в live-center одновременно идёт двадцать матчей, не все карточки требуют одинаковой частоты paint. Карточка в viewport с меняющимся счётом должна обновиться быстро; блок внизу страницы может получить тот же актуальный snapshot, но визуально применить его позже. Это не потеря данных, а разделение state freshness и render cadence.

Такой подход работает только при нормализованном store и устойчивых ключах. LiveCenter подписывается на конкретные entity, а не на весь массив событий. При возвращении карточки в viewport она читает последнюю версию, поэтому нет необходимости воспроизводить каждый пропущенный delta.

В профилировщике стоит искать путь stream message → store update → component commit → paint. Если дорого стоит уже первый шаг, memoization React-компонентов проблему не решит; если дорого второй — надо смотреть fan-out подписок и структуру state.

Профилировать участок «поток → отрисовка»

Серверный lag и frontend lag различаются. В клиентской телеметрии полезно отдельно измерять received_at→store_applied и store_applied→paint для sampled sessions. Тогда видно, где теряется актуальность.

Схема решения: Live-center во frontend: точечные обновления без лишних перерисовок
Диаграмма к разбору: Центр событий в интерфейсе: точечные обновления без лишних перерисовок.
Следующий слой

Связанные разборы по той же архитектурной цепочке

Архитектура

Состояние спортивного события: счёт, фазы и завершение матча

Как связать счёт, таймер и фазы матча в модель состояний, обрабатывать поздние сообщения поставщика и не допускать невозможных переходов.

Перейти к разбору →
Интерфейсы

Поиск по событиям и рынкам: отдельный индекс чтения

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

Перейти к разбору →
Надёжность

Сессия пользователя и публичный кэш: где проходит граница

Как отделить публичную страницу события от данных аккаунта, сохранить эффективность кэша и не допустить персональные данные в общий ответ.

Перейти к разбору →
Букмекерская логика

Кнопка коэффициента: доступность, изменение цены и приостановка

Как проектировать кнопку коэффициента: изменение цены, приостановка, фокус, клавиатурное управление и понятные состояния после выбора пользователя.

Перейти к разбору →