内置默认模型

这个示例使用 useSearchPagination() 的默认分页模型。Page size 选择器直接消费 useSearchPagination.pageSizeOptions;修改 page size 时,页码默认回到第 1 页。

这个示例只在页面初始化时检查一次页码溢出。这里可以这样做,是因为数据总数固定,并且 Pagination UI 会在调用 setPage 前限制目标页码;修改 page size 时,默认策略也会把页码重置为 1。因此,组件挂载后不会通过当前交互产生新的越界页。

你可以尝试手动修改当前页面的路由参数,比如 ?page=999,页码会自动修正。

这种“一次初始化检查”只适用于当前这类静态数据场景。如果 total 会随筛选、请求或数据修改发生变化,应在获得与当前查询匹配的可信 total 后重新调用 preventOverflow,不能只依赖初始化 effect。

#1
#2
#3
#4
#5
#6
#7
#8
#9
#10

1-10 of 123

Page size
(empty)
page: 1pageSize: 10
import { useSearchPagination } from '@guanriyue/decurl/pagination';
import { useEffect } from 'react';
import { useLocation } from 'react-router';

import { Badge } from '@/components/ui/badge';
import {
  Pagination,
  PaginationContent,
  PaginationEllipsis,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from '@/components/ui/pagination';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';

const items = Array.from({ length: 123 }, (_, index) => index + 1);

const toSearchText = (search: string): string => {
  return search.length > 0 ? search : '(empty)';
};

const getVisiblePages = (page: number, pageCount: number): number[] => {
  const start = Math.max(1, page - 1);
  const end = Math.min(pageCount, page + 1);

  return Array.from({ length: end - start + 1 }, (_, index) => start + index);
};

const UseSearchPaginationDemo = () => {
  const location = useLocation();
  const pagination = useSearchPagination();
  const pageCount = Math.max(1, Math.ceil(items.length / pagination.pageSize));
  const page = Math.min(pagination.page, pageCount);
  const startIndex = (page - 1) * pagination.pageSize;
  const currentItems = items.slice(
    startIndex,
    startIndex + pagination.pageSize,
  );
  const visiblePages = getVisiblePages(page, pageCount);
  const currentHref = `${location.pathname}${location.search}`;

  useEffect(() => {
    pagination.preventOverflow(items.length);
  }, [pagination.preventOverflow]);

  const updatePage = (nextPage: number) => {
    pagination.setPage(Math.min(Math.max(nextPage, 1), pageCount));
  };

  return (
    <section className="mx-auto w-full max-w-6xl space-y-5 p-4">
      <div className="grid min-h-44 grid-cols-3 content-start gap-2 sm:grid-cols-5 lg:grid-cols-10">
        {currentItems.map((item) => (
          <div
            key={item}
            className="flex h-9 items-center justify-center rounded-md bg-muted/50 font-mono text-xs"
          >
            #{item}
          </div>
        ))}
      </div>

      <div className="grid items-center gap-4 border-t pt-4 md:grid-cols-[1fr_auto_1fr]">
        <p className="text-sm text-muted-foreground">
          {startIndex + 1}-{startIndex + currentItems.length} of {items.length}
        </p>

        <Pagination className="w-auto">
          <PaginationContent>
            <PaginationItem>
              <PaginationPrevious
                href={currentHref}
                aria-disabled={page <= 1}
                className={cn(page <= 1 && 'pointer-events-none opacity-50')}
                onClick={(event) => {
                  event.preventDefault();
                  updatePage(page - 1);
                }}
              />
            </PaginationItem>

            {visiblePages[0] > 1 && (
              <>
                <PaginationItem>
                  <PaginationLink
                    href={currentHref}
                    onClick={(event) => {
                      event.preventDefault();
                      updatePage(1);
                    }}
                  >
                    1
                  </PaginationLink>
                </PaginationItem>
                {visiblePages[0] > 2 && (
                  <PaginationItem>
                    <PaginationEllipsis />
                  </PaginationItem>
                )}
              </>
            )}

            {visiblePages.map((visiblePage) => (
              <PaginationItem key={visiblePage}>
                <PaginationLink
                  href={currentHref}
                  isActive={visiblePage === page}
                  onClick={(event) => {
                    event.preventDefault();
                    updatePage(visiblePage);
                  }}
                >
                  {visiblePage}
                </PaginationLink>
              </PaginationItem>
            ))}

            {visiblePages[visiblePages.length - 1] < pageCount && (
              <>
                {visiblePages[visiblePages.length - 1] < pageCount - 1 && (
                  <PaginationItem>
                    <PaginationEllipsis />
                  </PaginationItem>
                )}
                <PaginationItem>
                  <PaginationLink
                    href={currentHref}
                    onClick={(event) => {
                      event.preventDefault();
                      updatePage(pageCount);
                    }}
                  >
                    {pageCount}
                  </PaginationLink>
                </PaginationItem>
              </>
            )}

            <PaginationItem>
              <PaginationNext
                href={currentHref}
                aria-disabled={page >= pageCount}
                className={cn(
                  page >= pageCount && 'pointer-events-none opacity-50',
                )}
                onClick={(event) => {
                  event.preventDefault();
                  updatePage(page + 1);
                }}
              />
            </PaginationItem>
          </PaginationContent>
        </Pagination>

        <div className="flex items-center gap-2 md:justify-end">
          <span className="text-sm text-muted-foreground">Page size</span>
          <Select
            value={String(pagination.pageSize)}
            onValueChange={(value) => {
              pagination.setPageSize(Number(value));
            }}
          >
            <SelectTrigger size="sm" className="w-20">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              {useSearchPagination.pageSizeOptions.map((pageSize) => (
                <SelectItem key={pageSize} value={String(pageSize)}>
                  {pageSize}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>
      </div>

      <div className="space-y-3 rounded-md bg-muted/40 p-4 text-sm">
        <code className="block break-all font-mono text-xs">
          {toSearchText(location.search)}
        </code>
        <div className="flex flex-wrap gap-2">
          <Badge variant="secondary">page: {pagination.page}</Badge>
          <Badge variant="secondary">pageSize: {pagination.pageSize}</Badge>
        </div>
      </div>
    </section>
  );
};

export default UseSearchPaginationDemo;

相关文档