Реализация пагинации без встроенных индексов

Idb-keyval — это минималистичная обёртка над IndexedDB, которая упрощает операции хранения данных в браузере. Основные методы включают get, set, del, clear и keys. Для организации пагинации без встроенных индексов важно понимать, что IndexedDB не гарантирует порядок ключей по умолчанию. Idb-keyval сохраняет данные в объектном хранилище с уникальными ключами, а перечисление всех ключей через keys() возвращает их в порядке добавления, что можно использовать для реализации собственного механизма постраничного отображения.

Организация хранения для пагинации

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

  1. Последовательные ключи Присваивать ключи в виде числовых последовательностей (1, 2, 3 …) или строковых с префиксом (item_1, item_2). Это позволит извлекать диапазоны значений по ключам, моделируя страницы.

  2. Массивы ключей для каждой страницы Хранить отдельный объект, содержащий массив ключей для каждой страницы. Например:

import { set, get } from 'idb-keyval';

async function savePage(pageNumber, items) {
  const keys = [];
  for (let i = 0; i < items.length; i++) {
    const key = `item_${pageNumber}_${i}`;
    await set(key, items[i]);
    keys.push(key);
  }
  await set(`page_${pageNumber}_keys`, keys);
}

Такой подход позволяет извлекать страницу целиком, не выполняя полный перебор всего хранилища.

Получение страницы данных

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

import { get } from 'idb-keyval';

async function getPage(pageNumber) {
  const keys = await get(`page_${pageNumber}_keys`);
  if (!keys) return [];
  const items = [];
  for (const key of keys) {
    const item = await get(key);
    items.push(item);
  }
  return items;
}

Такой метод минимизирует количество операций и не требует сортировки всех ключей на клиенте.

Динамическая постраничная загрузка

Для приложений с большим количеством данных часто применяется ленивое добавление страниц. Структура хранения при этом может включать:

  • Основной объект pages, содержащий номера страниц и массивы ключей
  • Отдельные записи данных по ключу
  • Механизм обновления страниц при добавлении новых элементов

Пример динамического добавления элементов:

import { get, set } from 'idb-keyval';

async function appendItem(item) {
  const lastPageNumber = (await get('last_page')) || 1;
  const keys = await get(`page_${lastPageNumber}_keys`) || [];
  const newKey = `item_${lastPageNumber}_${keys.length}`;
  await set(newKey, item);
  keys.push(newKey);

  // Проверка на размер страницы
  if (keys.length > 20) { // например, 20 элементов на страницу
    const nextPage = lastPageNumber + 1;
    await set(`page_${nextPage}_keys`, []);
    await set('last_page', nextPage);
  } else {
    await set(`page_${lastPageNumber}_keys`, keys);
    await set('last_page', lastPageNumber);
  }
}

Порядок элементов и контроль последовательности

Так как Idb-keyval не гарантирует встроенного порядка, важно:

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

Удаление элементов и корректировка страниц

Удаление элемента требует обновления массива ключей страницы:

import { get, set, del } from 'idb-keyval';

async function deleteItem(pageNumber, index) {
  const keys = await get(`page_${pageNumber}_keys`);
  if (!keys || index >= keys.length) return;
  const keyToDelete = keys.splice(index, 1)[0];
  await del(keyToDelete);
  await set(`page_${pageNumber}_keys`, keys);
}

При удалении можно объединять страницы или перераспределять элементы, чтобы поддерживать одинаковый размер страниц и корректное отображение.

Преимущества метода

  • Минимизация операций чтения: извлекаются только ключи страницы и их значения.
  • Гибкость управления страницами: можно легко изменять размер страниц и количество элементов.
  • Простая адаптация под разные структуры данных: работает с объектами, массивами, строками и числами.

Оптимизация работы с большим объёмом данных

Для больших хранилищ следует:

  • Кэшировать массивы ключей страниц в памяти при активной работе с пагинацией.
  • Использовать батчевые операции через Promise.all при извлечении нескольких значений для ускорения.
  • Разбивать страницы на фиксированные размеры, чтобы поддерживать предсказуемую производительность.

Пример батчевого чтения страницы:

import { get } from 'idb-keyval';

async function getPageBatch(pageNumber) {
  const keys = await get(`page_${pageNumber}_keys`);
  if (!keys) return [];
  const promises = keys.map(key => get(key));
  return await Promise.all(promises);
}

Итоговая архитектура

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

Такой подход позволяет реализовать полноценную постраничную загрузку данных в Idb-keyval, обходясь без встроенных индексов и сохраняя высокую производительность при росте объёма данных.