Что описывается: Apollo-client — популярная библиотека для работы с GraphQL. Библиотека призвана ускорить разработку и оптимизировать приложение.

Задача статьи: Описать возможные решения и проблемы оптимизации приложения в части apollo.

merge

В каких случаях использовать: если есть несколько запросов за одной и той же сущностью с разным набором полей. В некоторых случаях при реализации пагинации.

Что дает: Позволяет слить данные разных полей по одной и той же сущности в кэше apollo. Это необходимо, чтобы одновременно хранить данные по наибольшему количеству полей в кэше. Потому что при попытке чтения из кэша — apollo сначала проверит есть ли у него все данные в кэше. И если не хватает хотя бы одного поля будет выполнен запрос на сервер.

Что необходимо сделать: Проставить в typePolicies inMemoryCache флаг true для сущности/запроса. Либо написать свою собственную функцию по слиянию данных.

Пример: Классический пример — это пагинация и необходимость подгружать дополнительные данные на скролл или нажатие кнопки “показать еще”. Чтобы не подгружать все данные заново и не потерять уже загруженные, можно добавлять новые элементы в уже существующий массив. Другой пример, это список офферов, где мы показываем минимальную информацию и страница оффера с полной информацией. Без мержа при переходе со странице оффера на список, в кэше данные по офферу затрутся новой минимальной информацией. И если пользователь опять перейдет на страницу оффера, то необходимо будет делать новый запрос.

Ссылка на документацию

keyFields

В каких случаях использовать: если есть потребность сохранить сущность в кэше аполло. По умолчанию будут использоваться ключи id или _id, но можно передать массив своих полей или функцию для формирования ключа. Это может потребоваться, если надо записывать сущность по другому уникальному полю или нескольким полям.

Что дает: возможность в дальнейшем прочитать значения из кэша и сокращение количества запросов.

Что необходимо сделать: Для сущности в typePolicies описать поле keyFields. Важно, чтобы указанные поля были в запросах за этой сущностью.

Пример: вы отображаете на странице статистику по локации и типу жилья. При этом эта статистика у вас не хранится в базе и не имеет никакого уникального идентификатора. В таком случае можно указать поле region и type в качестве ключевых, и настроить функцию read для запроса этой статистики. Необходимо будет правильно связать параметры запроса с ключом сущности.

Ссылка на документацию

Read

В каких случаях использовать: В рамках оптимизации в случаях когда можно запрос и его параметры связать с сущностью кэша аполло.

Что дает: Позволяет сократить количество запросов, если в аполло уже есть все необходимые данные.

Что необходимо сделать: написать read-метод для соответствующего query, связав аргументы запроса с ключом сущности в аполло c помощью функции toReference.

Пример: Мы запросили список офферов и сохранили эти данные по ключу id в кэше аполло. Потом мы перешли на страницу оффера и хотим запросить данные по id. Для этого запроса мы можем связать id из параметра запроса с сущностью в apollo указав __typename и ключ по которому он записан в apollo.

Cсылка на документацию

keyArgs

В каких случаях использовать: в запросах за списком, если могут быть разные параметры запроса.

Что дает: Позволяет разделить списки для разных наборов параметров.

Что необходимо сделать: для query в typePolicies описать поле keyArgs. Можно передать массив или написать свою функцию.

Пример: есть запрос за списком товаров. В нем мы передаем набор фильтров и данные для пагинации(лимит и оффсет). Мы хотим чтобы список с разным набором фильтров хранился раздельно, при этом при переходе на другую страницу можно было слить текущий список и новые данные. Чтобы это было возможно как раз и надо указывать корректные keyArgs.

Ссылка на документацию

Fragments

В каких случаях использовать: Если есть логически связанный набор полей, особенно если этот набор переиспользуется в разных запросах.

Что дает: спасает от лишних запросов, если по ошибке не указал поле в одной из схем. Позволяет хранить схемы более читаемыми.

Пример: Есть сущность Offer. В ней есть набор информации описывающий положение объекта— например country, region, city. Офферы запрашиваются как список и в нем отображаются эти поля. Эти же поля нам необходимо отображать на странице оффера. В более сложных структурах, есть вероятность, что одно из полей забудется в запросе за оффером на странице. Это может быть критично, если это единственное поле, которого не хватало и спровоцирует новый запрос.

Ссылка на документацию

Fetch policy

В каких случаях использовать: Абсолютно во всех случаях запроса. По умолчанию используется cache-first. Она же и наиболее оптимальная стратегия, если не требуется всегда получать обновленные данные.

Что дает: позволяет управлять данными и запросами. В зависимости от указанного значения можно читать данные из кэша или всегда выполнять запрос на сервер. Полный список можно увидеть в документации.

Что необходимо сделать: Передать в useQuery нужную политику в параметр fetchPolicy.

Ссылка на документацию

SSR mode

В каких случаях использовать: Данные на SSR есть смысл запрашивать, если их необходимо отдать как можно скорее для SEO или пользователя и без них отданная страница не имеет большого смысла.

Что дает: Отключение выполнения запроса на сервере позволяет ускорить выдачу страницы, поскольку не надо будет ждать выполнения запроса. Данные загрузятся позже на стороне клиента.

Возможные проблемы: Если выполнять все запросы на стороне сервера, то во-первых увеличится время выдачи страницы. Во-вторых, размер страницы тоже будет увеличен засчет кэша аполло. Поэтому здесь стоит найти золотую середину.

Persisted queries

В каких случаях использовать: Если схемы не меняются долгое время и не формируются динамически.

Что дает: Позволяет сократить количество данных отправляемых пользователем. Дает возможность использовать get-запрос и благодаря этому закэшировать его на стороне CDN.

Что необходимо сделать: Добавить в цепочку линку созданную createPersistedQueryLink и передать туда хэширующую функцию.

Возможные проблемы: Если хэш еще не был сгенерирован или протух, то клиент выполнит 2 запроса. Первый упадет с ошибкой, второй выполнится с передачей всей схемы. Чтобы таких ошибок было немного можно увеличить время жизни хэша.

Ссылка на документацию
Еще одна ссылка

Prefetching

В каких случаях использовать: если есть большая вероятность, что пользователю потребуются эти данные и мы не хотим, чтобы пользователь долго ждал их загрузки.

Что дает: Улучшает UX за счет того, что пользователю не придется долго ждать выполнения запроса (он либо уже выполнен и надо только взять данные из кэша, либо скоро выполнится).

Что необходимо сделать: выполнить запрос с помощью lazy query на наведение, либо подгрузить эти данные сразу не блокируя при этом выдачу страницы.

Возможные проблемы: может выполняться очень много запросов и часть загруженных данных может не понадобиться совсем.

Пример: Есть список товаров. Мы хотим, чтобы пользователь как можно быстрее получал данные по товару на его странице. Можно в списке выполнить запрос по наведению мышки на элемент и тогда пользователь увидит страницу значительно быстрее. Другой пример — выпадающее меню с категориями товаров. Несмотря на то, что пользователь его может и не открыть мы все равно загружаем данные после загрузки страницы, чтобы при открытии сразу показать клиенту список.

Ссылка на документацию

Заключение

Эти пункты скорее памятка к работе с Apollo. Нам эти правила помогли сократить количество запросов, уменьшить количество передаваемых данных и ускорить выдачу страницы, а значит и улучшить метрики web vitals.

Комментарии (2)


  1. Fen1kz
    04.07.2023 12:56

    А с батчингом экспериментировали?

    Когда на странице есть х компонентов, каждый со своей кверей чтобы отправлять не х запросов, а один?

    Я вот все присматриваюсь, но не вчитывался: https://www.apollographql.com/docs/react/api/link/apollo-link-batch-http/


    1. silentnotes Автор
      04.07.2023 12:56

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