В данной статье я хотел бы привести пример практического кейса использования метода requestIdleCallback, который возник у меня на проекте. Кейс сам по себе небольшой, замеры времени отработки функции и отрисовки компонентов для использования производились с помощью React Profiler.
Хотелось бы сразу сказать, что статья может быть не чем-то новым, но может оказаться полезной в плане практического понимания того, где может пригодиться requestIdleCallback и как он может быть использован.
Коротко о проекте
Чтобы внести контекст, я упрощенно опишу проект в той части, с которой пришлось работать.
Это поисковик по различным материалам, выдача которых производится в таблице, количество столбцов и строк в которой можно регулировать. Соответственно, как и во всех поисковиках, присутствуют фильтры, которые организованы у нас в выезжающем по нажатию кнопки "Фильтры" сайдбару. В сайдбаре присутствуют несколько разворачивающихся списков с категориями фильтров (Основные, Параметр и т.п.) в которых лежат сами фильтры (тоже в свернутом виде, но с ними проблем нет). Фильтры могут быть разных типов: числовые, обычным списком чекбоксов для выбора, дерево фильтров (иерархия с зависимостями от родительского фильтра) и т.п.
Для наглядности скрин сайдбара с фильтрами
Теперь к делу
Недавно на проекте появилось немного свободного времени и на меня упала задача прошерстить код и выделить части проекта, которые необходимо отрефакторить и выписать все это в отдельную задачу тех. долга (и возможно что-то сразу пофиксить).
Я, недолго думая, пошел смотреть фильтры, так как знал, что там могут быть определенные проблемы. Это связано с тем, что когда-то давно, когда я только пришел на проект с ними уже была одна проблема. Она заключалась в том, что при большом количестве фильтров сайдбар после нажатия открывался через время, которое было чуть больше секунды, что очень долго.
Причина была в том, что происходил расчет (распределение по типам и раскидывания данных по компонентам для каждого типа) и отрисовка даже в свернутых категориях фильтров. Такой подход помогал заранее рассчитать все категории фильтров и разворачивать их без задержки, но когда фильтров стало много вылезла такая проблема.
Поправил я это достаточно просто на тот момент - поставил расчет с отрисовкой по условию, что если открыт, то считай. Открываться стало около 100мс, что очень хорошо.
Сделав так, я пожертвовал расчетами, которые происходили заранее и, как итог, замедлил открытие самих категорий, так как теперь расчеты происходили при их открытии, а значит появилась небольшая задержка перед тем, как список развернется. В тот момент, когда я это делал, фильтров было много, но должно было стать еще больше, так что решение было временным.
Время пришло...
И вот настал момент, когда появилось время на рефакторинг и когда фильтров начало становиться больше и в ближайшее время обещало увеличиться в достаточно большое количество (так как количество материалов с разными параметрами увеличивалось).
Один из самых многочисленных категорий фильтров - это Параметр (на скрине в спойлере в самом начале категория идет второй и по умолчанию свернута). При нажатии на нее расчет и открытие занимает около 100мс (да, на данный момент вроде как не так много, но небольшая задержка видна глазу, а если количество фильтров в категории увеличится, то и задержка вырастет).
Я начал думать, как можно решить данную проблему. И я придумал вариант, как можно это исправить - надо рассчитывать открытую категорию перед открытием сайдбара (как сейчас и происходит), а остальные категории рассчитывать не при нажатии на них, а заранее, после того, как сайдбар откроется.
Как это можно сделать?
Если чуть углубиться, то вся проблема возникает из-за того, что расчеты блокируют работу браузера и из-за этого все задержки. Значит надо отложить расчеты закрытых категорий, пока сайдбар не откроется.
По описанию такое действие как раз выполняет requestIdleCallback - данный метод откладывает выполнение задачи до тех пор, пока браузер не освободится от других задач, а значит это то, что нам нужно.
Каким образом я его применил?
Я вынес маппинг данных с расчетами и передачей в отрисовывающийся компонент в отдельную функцию, которая помещает данные в переменную, которая уже помещалась в код для отрисовки. И в зависимости от того, открыта ли категория, функция выполнялась либо сразу, либо помещалась в requestIdleCallback.
При таком подходе мы получили то, что теперь категория рассчитывалась заранее и стала открываться около 3мс.
Что в итоге?
В итоге мы получили то, что фильтры открываются на данный момент достаточно быстро и взаимодействие с ними при открытии различных категорий не затормаживается, а значит не вызывает визуального дискомфорта пользователей. Раньше категории разворачивались около 100мс, а сейчас около 3мс, а значит взаимодействие ускорилось где-то в 30 раз и как бы не казалось, что это маленькие цифры (хоть и порядок разницы большой), в будущем это может существенно сыграть в нашу пользу.
На данный момент requestIdleCallback все еще не поддерживается в Safari, но в нашем случае это не проблема, так как на проект вход идет с внутреннего контура и IOS там даже не пахло) В случае, если же все-таки есть пользователи с Safari я бы наверное попробовал провернуть что-то такое с использованием setTimeout.
Надеюсь эта статья будет кому-нибудь полезна для понимания использования и влияния requestIdleCallback и буду еще больше рад, если вы сможете привести дополнительные примеры использования данного метода.
Комментарии (4)
xeleos
06.09.2023 15:51На просторах гитхаба для сафари нашёл что-то типо полифила, хотя работает оно скорее всего не так же, т.к. там используется setTimeout.
idmx Автор
06.09.2023 15:51Да, тоже его видел, там действительно на setTimeout и это скорее на данный момент единственное похожее решение (по крайней мере я других не вижу пока что). Когда нибудь и они поддержат этот метод:)
ruslan_astratov
Спасибо. Было полезно!