Skip to content

Пагинация

При работе с большими наборами данных важно использовать пагинацию. Kodzero SDK предоставляет удобные инструменты для постраничной навигации.

Базовая пагинация

Метод findMany

Для простой пагинации используйте параметры page и perPage:

typescript
// Первая страница, 10 записей
const page1 = await User.findMany({
  page: 1,
  perPage: 10
})

// Вторая страница
const page2 = await User.findMany({
  page: 2,
  perPage: 10
})

Продвинутая пагинация

Метод findManyPaginated

Для полноценной работы с пагинацией используйте findManyPaginated, который возвращает объект PaginatedResult:

typescript
const result = await User.findManyPaginated({}, 1, 25)

// Данные
console.log(result.data)  // массив пользователей

// Состояние пагинации
console.log(result.state.page)       // текущая страница
console.log(result.state.total)      // всего документов
console.log(result.state.totalPages) // всего страниц
console.log(result.state.limit)      // документов на странице
console.log(result.state.left)       // осталось документов

Объект PaginatedResult

СвойствоТипОписание
dataT[]Массив документов текущей страницы
state.pagenumberНомер текущей страницы
state.totalnumberОбщее количество документов
state.totalPagesnumberОбщее количество страниц
state.limitnumberКоличество документов на страницу
state.skipnumberКоличество пропущенных документов
state.leftnumberКоличество оставшихся документов

Навигация по страницам

PaginatedResult предоставляет методы для навигации:

typescript
const result = await User.findManyPaginated({}, 1, 25)

// Переход к следующей странице
await result.next()
console.log(result.data)        // данные новой страницы
console.log(result.state.page)  // 2

// Переход к предыдущей странице
await result.previous()
console.log(result.state.page)  // 1

Примеры использования

Простой список с пагинацией

typescript
async function loadUsers(page: number = 1) {
  const result = await User.findManyPaginated({}, page, 10)
  
  return {
    users: result.data,
    pagination: {
      currentPage: result.state.page,
      totalPages: result.state.totalPages,
      totalItems: result.state.total,
      hasNextPage: result.state.page < result.state.totalPages,
      hasPrevPage: result.state.page > 1
    }
  }
}

React компонент с пагинацией

tsx
import { useState, useEffect } from 'react'

function UserList() {
  const [paginatedResult, setPaginatedResult] = useState(null)
  const [loading, setLoading] = useState(false)

  const loadPage = async (page: number) => {
    setLoading(true)
    const result = await User.findManyPaginated({}, page, 10)
    setPaginatedResult(result)
    setLoading(false)
  }

  useEffect(() => {
    loadPage(1)
  }, [])

  if (loading) return <div>Загрузка...</div>
  if (!paginatedResult) return null

  const { data, state } = paginatedResult

  return (
    <div>
      {/* Список пользователей */}
      <ul>
        {data.map(user => (
          <li key={user._id}>{user.name}</li>
        ))}
      </ul>

      {/* Пагинация */}
      <div className="pagination">
        <button 
          disabled={state.page === 1}
          onClick={() => loadPage(state.page - 1)}
        >
          Назад
        </button>
        
        <span>
          Страница {state.page} из {state.totalPages}
        </span>
        
        <button 
          disabled={state.page >= state.totalPages}
          onClick={() => loadPage(state.page + 1)}
        >
          Вперёд
        </button>
      </div>
      
      <p>Всего записей: {state.total}</p>
    </div>
  )
}

Бесконечная прокрутка

typescript
class InfiniteList {
  private result: PaginatedResult<User> | null = null
  private allItems: User[] = []

  async loadInitial() {
    this.result = await User.findManyPaginated({}, 1, 20)
    this.allItems = [...this.result.data]
    return this.allItems
  }

  async loadMore() {
    if (!this.result || !this.hasMore()) {
      return this.allItems
    }

    await this.result.next()
    this.allItems = [...this.allItems, ...this.result.data]
    return this.allItems
  }

  hasMore(): boolean {
    if (!this.result) return false
    return this.result.state.page < this.result.state.totalPages
  }

  get items() {
    return this.allItems
  }
}

// Использование
const list = new InfiniteList()
await list.loadInitial()

// При скролле вниз
if (list.hasMore()) {
  await list.loadMore()
}

Vue 3 composable

typescript
import { ref, computed } from 'vue'

export function usePagination<T>(
  model: any,
  options = {},
  perPage = 10
) {
  const result = ref<PaginatedResult<T> | null>(null)
  const loading = ref(false)
  const error = ref<Error | null>(null)

  const items = computed(() => result.value?.data || [])
  const currentPage = computed(() => result.value?.state.page || 1)
  const totalPages = computed(() => result.value?.state.totalPages || 0)
  const total = computed(() => result.value?.state.total || 0)
  const hasNext = computed(() => currentPage.value < totalPages.value)
  const hasPrev = computed(() => currentPage.value > 1)

  async function loadPage(page: number) {
    loading.value = true
    error.value = null
    try {
      result.value = await model.findManyPaginated(options, page, perPage)
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  async function next() {
    if (hasNext.value) {
      await loadPage(currentPage.value + 1)
    }
  }

  async function prev() {
    if (hasPrev.value) {
      await loadPage(currentPage.value - 1)
    }
  }

  return {
    items,
    currentPage,
    totalPages,
    total,
    hasNext,
    hasPrev,
    loading,
    error,
    loadPage,
    next,
    prev
  }
}

// Использование в компоненте
const {
  items,
  currentPage,
  totalPages,
  loading,
  next,
  prev,
  loadPage
} = usePagination(User, { sort: '-createdAt' }, 10)

onMounted(() => loadPage(1))

Комбинирование с фильтрами

Пагинацию можно комбинировать с другими параметрами запроса:

typescript
const result = await Product.findManyPaginated(
  {
    search: 'ноутбук',
    sort: '-price',
    fields: ['name', 'price', 'category']
  },
  1,  // страница
  20  // на страницу
)

Следующие шаги

Опубликовано под лицензией ISC.