Мы столкнулись с медленным рендерингом списков, состоящих из 200+ элементов в проекте на ReactJS (15.1.0) + react-router + redux.
К примеру, есть список пользователей, который представляет собой статические "карточки", т.е. блок с фото, парой ссылок и небольшим текстом. Пользователей много, поэтому при монтировании компонента отображаются первые 30, а остальные по 30 подгружаются c api-сервера по запросу (т.е. по кнопке "показать еще"). Каждая такая карточка это статический компонент, shouldComponentUpdate() которого всегда возвращает false. Компонент списка подписан на обновления через connect() и отрисовывает карточки только если получены новые 30. Пока все хорошо и по мере загрузки данных с бэкенда и отрисовки карточек скорость рендеринга остается стабильной - судя по данным Perf происходит рендеринг только новых 30 экземпляров компонента карточки с общим временем порядка 80-90мс.
Проблема появляется тогда, когда после загрузки относительно небольшого списка (уже от 200-300 карточек) пользователь переходит по ссылке в карточке (компонент Link из react-router), а потом возвращается назад по кнопке браузера.
В этом случае компонент списка заново монтируется и ему приходится перерисовывать сразу все те 200-300 карточек, находящихся в state редакса. При этом скорость рендеринга падает пропорционально кол-ву карточек, т.е. практически в 10 раз, и если на десктопе это терпимо, то для мобильного пользователя время перерисовки страницы составляет 5+ секунд. И это при относительно небольшом кол-ве данных. Кроме того, все приложение (как и, например, redux dev tools в этот момент) "замерзает", т.е. нет возможности как-то отображать, к примеру, индикатор прогресса рендеринга.
Вопрос в следующем - нормальное ли это время рендеринга для реакта при таких условиях? Если да, то какие могут быть способы решения проблемы?
Есть ли возможность каким-либо образом кешировать результаты рендеринга смонтированного ранее компонента? Какие вообще существуют варианты решения проблемы длинных списков, если размер элемента заранее неизвестен?
Уточню проблему - реакт медленно рендерит статический список из нескольких тысяч html элементов - т.е. обычную статику типа some textanother text. Прямая вставка в DOM в десятки раз быстрее.
Спасибо, но все решения такого рода не подойдут - не известна высота блоков, т.к. там может быть разное кол-во текста и разная высота фото. Кроме того, список строится в виде masonry layout, что еще больше усложняет задачу расчета высоты плейсхолдеров.
хм, мне кажется зря игнорите решения "такого рода", особенно при подгрузке в бесконечный скролл
вот есть интересное - элементы разной высоты, да и вообще хоть в шахматном порядке
последний пример 99.999.999 !!! элементов devblog.orgsync.com/react-list (первые два пропустите - там просто подгрузка в конец)
остальные как раз по вашей теме...
да и код бы выложили на фидле какой, глядишь и дело бы быстрей пошло, вдруг у вас где нить запятая не в том месте стоит (ну эт так - к примеру)
Спасибо, посмотрю еще раз. Тестовый код - https://jsfiddle.net/86e855mh/ Скорость рендеринга падает пропорционально сложности верстки, кроме того, по Timeline цифры в несколько раз больше, чем console.time(). На чистом js рендеринг в ~5 раз быстрее. Но, судя по всему, это то, как работает реакт...
пока тока цифры... вдруг кому интересно не смотря в консоль ))
по таймлайну и по консоли время сходится
React.render выполняется например 530ms
после этого еще recalculate style 45ms
а уже после этого Layout 202ms
итого 777ms
202ms - это уже рендер самим браузером 1000 элементов, каждый из которых это 10 простых дивов, спанов и прочее
итого 10К нод
т.е. тут ничего не сделаешь... да?
ну тут я вернусь к своему предложению юзать виртуальный список
т.е. будет пару элементов * 10нод = хх нод - почти мгновенно
тогда как бы должно увеличится время расчета, НО
но и список то тоже будет не весь рендерится
будет ЖЕ выборка именно этих НЕСКОЛЬКИХ элементов списка
как вариант без использования сторонних "виртуальных-листов"
отрисовать первые 10 +- (иль 30 как там у вас)
и по таймауту с минимальной задержкой отрисовать ВСЕ (т.е. первые 30 не обновятся реактом, а останутся в доме не тронутыми)
и еще в качестве дополнения... хотя не знаю как повлиет на отрисовку то
вдруг хоть чутка поможет )) ну хоть сотачку ms бы...
из доки по реакту
Use the production build
If you're benchmarking or seeing performance problems in your React apps, make sure you're testing with the minified production build. The development build includes extra warnings that are helpful when building your apps, but it is slower due to the extra bookkeeping it does.
Да, спасибо, реактом создаем контейнер и получаем его ref, а содержимое наполняем через unstable_renderSubtreeIntoContainer() + заполняем контейнер отрендереными компонентами чистым javascript.
*блок с фото* - это скорее всего и есть проблема тормозов. Если так, то можно подумать в сторону того чтобы инлайнить эти картинки, например.
Ну и самый общий способ - с помощью дизайна UI ограничить видимую область и отображать только те записи которые непосредственно видны на экране. В вашем примере по кнопке "назад" нужно опять сбрасывать состояние компонента таким образом чтобы он отображал 30 записей.
отклик 5 секунд - это ненормально вне зависимости от того react у вас или что-то другое. Приемлемым считается отклик до 1с.
Дело не в фото, т.к. без них та же ситуация. Сбрасывать список по кнопке назад можно, но это как раз один из тех костылей, за который не любят бесконечные списки.
Все дело в том, что на vanilla js список отрисовывается в разы быстрее, как и должен, поэтому возник вопрос почему реакт это делает так долго, не смотря на то, что никакого сравнения виртуального DOM с реальным в этом случае не нужно.
Кстати, сейчас припоминаю что у меня похожее поведение тоже когда-то было по кнопке назад, но не всегда а лишь изредка подтормаживало. Это конечно предположение, но возможно проблема в реакт-роутере? А просто рендер 200 компонентов без "назад" тормозит?
Да, просто рендер тоже тормозит. Т.е. если просто смонтировать компонент с несколькими сотнями элементов (в сумме пара тысяч нод). И чем их больше, тем больше тормозит. Что, в принципе, нормально и понятно, но не понятно, почему это все начинается на относительно небольших списках.
>Если да, то какие могут быть способы решения проблемы?
Рендерить без реакта, особенно если там всё статическое, то будет не так уж и сложно. Главное определиться под какие движки оптимизировать, тк с разными движками нужно по-разному работать с домом для получения наиболее производительного результата.
Да ну, вроде банальная проблема, которая много где встречается и не решается.
Кэширование, как мне кажется, не вариант. Забьется оперативная память и начнутся тормоза на мобилке.
У меня вк, когда налистаешь стену на несколько сотен постов, очень люто тормозит, даже при переходе на другие страницы - видимо он данные кеширует и спасает только перезагрузка
У вас много картинок. И все эти сотни картинок, заново загружаются. Вот пара вариантов:
1. кешировать картинки.
2. отображать контент, а потом уже подгружать требуемые картинки, по мере неоходимости.
3. грузить миниатюры для мобилок и кешировать их
4. любой другой способ сократить кол-во закгрузок картинок.
Мой опыт разработки фроненда всего месяц, так что возможно сморожу глупость.
Рискну предположить что тормозит не отрисовка как таковая, а вычисление изменений между VirtualDOM и DOM для внесения изменений.
Мне кажется можно поиграться с параметром key у компонентов карточки, чтобы в одном случае он был постоянным между отрисовками, а во втором случае уникальным, чтобы он считал, что все изменилось и просто выкинул все дерево и добавил новое и сравнить результаты.
судя по данным Perf происходит рендеринг только новых 30 экземпляров компонента карточки с общим временем порядка 80-90мс
Это не нормальное время для рендринга пачки, вероятнее всего у вас в потоке рендера присутсвуют блокирующие запросы (например синхронное получение каритки) или тяжелые расчеты.
Можно посмотреть на таймлайн этого процесса и разобраться в чем может быть причина.