You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

API无内置分页机制,如何用Apollo/GraphQL高阶组件实现分页?

嘿,好问题!虽然你当前使用的API没指定分页机制,但Apollo Client确实提供了几种开箱即用的分页方案,能帮你优雅处理大量书籍数据的加载。下面我就给你拆解两种最常用的实现方式,以及怎么适配你的现有代码:

常见的Apollo分页方案

1. 偏移分页(Offset-Based Pagination)

这是最直观的分页方式,核心是用offset(起始位置)和limit(每页数量)两个参数来控制返回的数据范围。

首先你需要修改GraphQL查询,新增这两个参数:

export const books = gql`
  query Query($offset: Int!, $limit: Int!) {
    books(offset: $offset, limit: $limit) {
      title
    }
  }
`;

接下来在组件里管理分页状态——如果是函数组件,我们可以用useState配合Apollo的useQuery钩子实现:

import { useQuery } from '@apollo/client';
import { useState } from 'react';
import { books } from './your-query-path';

function BookList() {
  // 初始化偏移量为0,每页显示10本书
  const [offset, setOffset] = useState(0);
  const limit = 10;

  const { loading, error, data } = useQuery(books, {
    variables: { offset, limit },
  });

  if (loading) return <p>加载中...</p>;
  if (error) return <p>出错了:{error.message}</p>;

  return (
    <div>
      <ul>
        {data.books.map((book, idx) => (
          <li key={idx}>{book.title}</li>
        ))}
      </ul>
      {/* 分页按钮 */}
      <div>
        {offset > 0 && (
          <button onClick={() => setOffset(prev => prev - limit)}>上一页</button>
        )}
        <button onClick={() => setOffset(prev => prev + limit)}>下一页</button>
      </div>
    </div>
  );
}

export default BookList;

如果你还是想用原来的高阶组件(graphql HOC)方式,也可以在组件内部维护offset和limit的state,再通过变量传递给查询。

不过要注意:偏移分页在数据频繁插入/删除时,可能会出现重复或遗漏数据的问题(比如在你翻页期间,有新书籍插入到前面,下一页就可能重复加载之前的内容)。如果你的书籍数据经常变动,更推荐下面的游标分页。

2. 游标分页(Cursor-Based Pagination)

这是GraphQL官方推荐的分页方案,用一个唯一的“游标”(比如书籍的ID、创建时间戳)标记当前分页的位置,能避免偏移分页的弊端。

首先修改查询,新增after(游标标记)和first(每页数量)参数,同时让API返回分页元数据(pageInfo)和包含游标与书籍数据的edges:

export const books = gql`
  query Query($after: String, $first: Int!) {
    books(after: $after, first: $first) {
      edges {
        node {
          title
        }
        cursor
      }
      pageInfo {
        hasNextPage  # 是否还有下一页
        endCursor    # 当前页最后一条数据的游标
      }
    }
  }
`;

然后在组件里用Apollo的fetchMore方法加载下一页,并合并数据:

import { useQuery } from '@apollo/client';
import { useState } from 'react';
import { books } from './your-query-path';

function BookList() {
  const [cursor, setCursor] = useState(null);
  const first = 10;

  const { loading, error, data, fetchMore } = useQuery(books, {
    variables: { after: cursor, first },
  });

  if (loading) return <p>加载中...</p>;
  if (error) return <p>出错了:{error.message}</p>;

  const handleLoadMore = () => {
    fetchMore({
      variables: { after: data.books.pageInfo.endCursor },
      // 合并新旧数据
      updateQuery: (prevResult, { fetchMoreResult }) => {
        if (!fetchMoreResult) return prevResult;
        return {
          books: {
            ...fetchMoreResult.books,
            edges: [...prevResult.books.edges, ...fetchMoreResult.books.edges],
          },
        };
      },
    });
  };

  return (
    <div>
      <ul>
        {data.books.edges.map(({ node }, idx) => (
          <li key={idx}>{node.title}</li>
        ))}
      </ul>
      {/* 只有还有下一页时显示按钮 */}
      {data.books.pageInfo.hasNextPage && (
        <button onClick={handleLoadMore}>加载更多</button>
      )}
    </div>
  );
}

export default BookList;

关键注意点

  • 不管用哪种分页方式,后端API必须支持对应的参数(比如offset/limit或者after/first)。如果后端现在不支持,你需要先修改后端的GraphQL Schema和Resolver,添加这些参数的处理逻辑。
  • 如果后端暂时没法修改,你也可以在前端做“假分页”:先一次性获取所有书籍,再用前端state控制显示范围。但这种方式只适合数据量小的场景,数据量大的话会严重影响页面性能。

内容的提问来源于stack exchange,提问作者Ilja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:34:10