В итоге это оказалась неприятная «гонка условий» между «сигнальным элементом» (sentinel), который мы используем для определения момента, когда нужно выполнить действие «загрузить ещё», и отрисовкой строк в каталоге пользователей ![]()
Проблема
При наличии не менее 50 пользователей страница /u (каталог пользователей) сразу при первой загрузке вызывала loadMore, ещё до того, как пользователь успевал прокрутить страницу вниз. Это приводило к автоматической загрузке нежелательной второй страницы результатов.
Коренная причина
Гонка условий по времени при первоначальной загрузке страницы:
- Пользователь переходит на
/u controllers/usersначинает загрузку данных (isLoading: true)- Шаблон отрисовывается с
ConditionalLoadingSpinner, показывающим индикатор загрузки - Данные загружаются,
isLoadingстановитсяfalse - Индикатор загрузки скрывается,
DirectoryTableначинает отрисовку 50 пользователей - В DOM вставляется сигнальный элемент LoadMore
- Создаётся IntersectionObserver и сразу начинает отслеживание
- В этот момент таблица всё ещё вычисляет макет и расширяется до полной высоты
- Сигнальный элемент на короткое время оказывается в области видимости (~292 пикселя от верха) до того, как таблица расширится
- Наблюдатель обнаруживает пересечение → запускает
loadMore
- Таблица завершает расширение до полной высоты (~3689 пикселей)
- Сигнальный элемент перемещается в правильное положение (~3959 пикселей, ниже области видимости)
Наблюдатель был «слишком нетерпелив» — он начал отслеживание до завершения разметки контента, зафиксировав сигнальный элемент в тот краткий момент, когда таблица ещё не достигла своей конечной высоты.
Исправление
Задержка создания наблюдателя до готовности контента:
Добавлен параметр @isLoading в LoadMore, который предотвращает создание IntersectionObserver, пока контент ещё загружается.
Как это работает теперь
Загрузка страницы → isLoading=true → Модификатор пропускает создание наблюдателя
↓
Загрузка данных → isLoading=false → Модификатор перезапускается, создаёт наблюдатель
↓
Таблица полностью расширена → Сигнальный элемент в правильном положении (ниже области видимости)
↓
Пользователь прокручивает вниз → Сигнальный элемент попадает в область видимости → loadMore срабатывает ✓