Пагинация
Кратко (TL;DR): Используйте ссылки пагинации, предоставляемые JSON:API. Никогда не создавайте URL пагинации вручную.
Пагинация может быть обманчиво сложной темой. Легко допустить ошибки и не следовать лучшим практикам. Эта страница поможет вам реализовать пагинацию правильно, что сделает ваш клиент более надёжным и готовым к будущим изменениям.
Как?
Каждый ответ JSON:API с пагинацией содержит встроенные ссылки пагинации. Пример:
{
"data": [
{"type": "sample--type", "id": "abcd-uuid-here"},
{"type": "sample--type", "id": "efgh-uuid-here"}
],
"links": {
"self": "?page[offset]=3&page[limit]=3",
"next": "?page[offset]=6&page[limit]=3",
"prev": "?page[offset]=0&page[limit]=3"
}
}
self
: текущая страница.next
: следующая страница.prev
: предыдущая страница.
Наличие или отсутствие этих ссылок имеет значение:
- Если есть
next
— есть следующая страница. - Если
next
нет — вы на последней странице. - Если есть
prev
— вы не на первой странице. - Если нет
next
иprev
— страница одна.
Даже если задан page[limit]=3
, может вернуться только 2 ресурса — например, если один из них удалён из-за отсутствия доступа. Это нормально и ожидаемо.
Пример плохой реализации (наивный подход)
const baseUrl = 'http://example.com';
const path = '/jsonapi/node/content';
const pager = 'page[limit]=5';
const filter = `filter[field_premium][value]=${user.isSubscriber()}`;
fetch(`${baseUrl}${path}?${pager}&${filter}`)
.then(resp => resp.ok ? resp.json() : Promise.reject(resp.statusText))
.then(document => listComponent.setContent(document.data))
.catch(console.log);
Проблемы:
- Фильтр избыточен — сервер сам отфильтрует запрещённый контент.
- Предполагается, что придёт 5 элементов, но может прийти меньше.
Улучшенная реализация
const listQuota = 5;
const content = [];
const baseUrl = 'http://example.com';
const path = '/jsonapi/node/content';
const pager = `page[limit]=${listQuota}`;
const getAndSetContent = (link) => {
fetch(link)
.then(resp => resp.ok ? resp.json() : Promise.reject(resp.statusText))
.then(document => {
content.push(...document.data);
listContent.setContent(content.slice(0, listQuota));
const hasNextPage = document.links.hasOwnProperty("next");
if (content.length = listQuota && hasNextPage) {
getAndSetContent(document.links.next);
}
const nextPageLink = hasNextPage ? document.links.next : null;
if (content.length > listQuota || hasNextPage) {
listComponent.showNextPageLink(nextPageLink);
}
})
.catch(console.log);
}
getAndSetContent(`${baseUrl}${path}?${pager}`);
Преимущества:
- Фильтр убран — безопасность реализована на стороне сервера.
- Проверяется реальное количество полученных данных.
- Переход к следующей странице возможен, если есть
next
или если получено больше 5 элементов.
Важно: ссылки "следующая страница" в HTML не должны совпадать с API-страницами. Лучше использовать ссылки из API как есть.
Часто задаваемые вопросы
Почему нельзя задать лимит больше 50?
JSON:API должен проверять доступ к каждому элементу ответа. При лимите в 200 000 это может привести к перегрузке и ошибкам. Лимит 50 был установлен как безопасный компромисс. Если нужно больше — используйте модуль JSON:API Page Limit.
Почему количество ресурсов в ответе меньше, чем лимит?
page[limit]
— это максимум, а не гарантия. Если некоторые ресурсы недоступны пользователю (например, неопубликованные узлы), они будут удалены из ответа. Это поведение по умолчанию для повышения безопасности.
Статья с сайта Drupal Documentation.