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

如何在Ant Design Table分页中正确使用GraphQL Relay连接游标?

我经常遇到这种Ant Design Table和GraphQL游标分页适配的需求,其实核心就是把Table默认的页码驱动分页逻辑转换成游标驱动的逻辑,下面给你一步步拆解正确的实现方式:

核心适配思路

Ant Design Table默认依赖「页码+总条数」来做分页,但GraphQL游标分页的核心是「游标(cursor)+ 是否有更多数据」,所以我们需要:

  1. 抛弃Table默认的页码、总页数计算逻辑
  2. 自定义分页交互(要么替换Table自带的分页组件,要么自己写独立的分页控件)
  3. 用GraphQL返回的pageInfo(比如hasNextPage/endCursor)来控制分页按钮的状态
  4. 发起请求时传递游标参数,而非页码和每页条数

1. 初始化状态管理

首先在组件里维护游标分页必需的状态:

import { useState, useEffect } from 'react';
import { Table, Button } from 'antd';

const YourTableComponent = () => {
  // 表格数据源
  const [dataSource, setDataSource] = useState([]);
  // 游标分页核心状态
  const [paginationState, setPaginationState] = useState({
    endCursor: null,       // 当前页最后一条数据的游标(用于请求下一页)
    hasNextPage: false,    // 是否还有下一页
    startCursor: null,     // 当前页第一条数据的游标(用于请求上一页)
    hasPreviousPage: false,// 是否还有上一页
    loading: false,        // 请求加载状态
    pageSize: 10           // 每页条数(固定值,传给GraphQL接口)
  });

2. 配置Table,禁用默认分页

把Table自带的分页功能关掉,自己控制分页交互会更灵活:

// 表格列配置(替换成你的业务列)
const columns = [
  { title: 'ID', dataIndex: 'id', key: 'id' },
  { title: '名称', dataIndex: 'name', key: 'name' },
  // ...其他列
];

// 渲染表格
return (
  <>
    <Table
      columns={columns}
      dataSource={dataSource}
      rowKey="id"
      pagination={false} // 关闭默认分页
      loading={paginationState.loading}
    />

    {/* 自定义游标分页控件 */}
    <div style={{ textAlign: 'center', marginTop: 16 }}>
      <Button
        disabled={!paginationState.hasPreviousPage || paginationState.loading}
        onClick={fetchPreviousPage}
        style={{ marginRight: 8 }}
      >
        上一页
      </Button>
      <Button
        disabled={!paginationState.hasNextPage || paginationState.loading}
        onClick={fetchNextPage}
      >
        下一页
      </Button>
    </div>
  </>
);

3. 实现GraphQL请求逻辑

对接你的GraphQL接口,根据游标参数请求对应页面的数据。假设你的GraphQL查询结构是Relay风格的:

query GetItems($first: Int, $after: String, $last: Int, $before: String) {
  items(first: $first, after: $after, last: $last, before: $before) {
    edges {
      node { id, name } // 你的业务字段
    }
    pageInfo {
      hasNextPage
      endCursor
      hasPreviousPage
      startCursor
    }
  }
}

对应实现请求函数:

// 加载第一页(无游标参数)
const fetchFirstPage = async () => {
  setPaginationState(prev => ({ ...prev, loading: true }));
  try {
    const response = await yourGraphQLClient.request(GetItems, {
      first: paginationState.pageSize
    });
    const { edges, pageInfo } = response.items;
    setDataSource(edges.map(edge => edge.node));
    setPaginationState({
      ...paginationState,
      endCursor: pageInfo.endCursor,
      hasNextPage: pageInfo.hasNextPage,
      startCursor: pageInfo.startCursor,
      hasPreviousPage: pageInfo.hasPreviousPage,
      loading: false
    });
  } catch (err) {
    console.error('加载数据失败:', err);
    setPaginationState(prev => ({ ...prev, loading: false }));
  }
};

// 加载下一页(传入endCursor)
const fetchNextPage = async () => {
  if (!paginationState.hasNextPage || paginationState.loading) return;
  setPaginationState(prev => ({ ...prev, loading: true }));
  try {
    const response = await yourGraphQLClient.request(GetItems, {
      first: paginationState.pageSize,
      after: paginationState.endCursor
    });
    const { edges, pageInfo } = response.items;
    setDataSource(edges.map(edge => edge.node));
    setPaginationState({
      ...paginationState,
      endCursor: pageInfo.endCursor,
      hasNextPage: pageInfo.hasNextPage,
      startCursor: pageInfo.startCursor,
      hasPreviousPage: pageInfo.hasPreviousPage,
      loading: false
    });
  } catch (err) {
    console.error('加载下一页失败:', err);
    setPaginationState(prev => ({ ...prev, loading: false }));
  }
};

// 加载上一页(传入startCursor)
const fetchPreviousPage = async () => {
  if (!paginationState.hasPreviousPage || paginationState.loading) return;
  setPaginationState(prev => ({ ...prev, loading: true }));
  try {
    const response = await yourGraphQLClient.request(GetItems, {
      last: paginationState.pageSize,
      before: paginationState.startCursor
    });
    const { edges, pageInfo } = response.items;
    setDataSource(edges.map(edge => edge.node));
    setPaginationState({
      ...paginationState,
      endCursor: pageInfo.endCursor,
      hasNextPage: pageInfo.hasNextPage,
      startCursor: pageInfo.startCursor,
      hasPreviousPage: pageInfo.hasPreviousPage,
      loading: false
    });
  } catch (err) {
    console.error('加载上一页失败:', err);
    setPaginationState(prev => ({ ...prev, loading: false }));
  }
};

// 组件挂载时加载第一页数据
useEffect(() => {
  fetchFirstPage();
}, []);

额外优化建议

  1. 无限滚动适配:如果不需要分页按钮,想用滚动加载,直接用Ant Design的InfiniteScroll组件包裹Table即可,完美适配游标分页的场景。
  2. 页面数据缓存:如果用户频繁切换上下页,可以把请求过的页面数据缓存起来,避免重复请求。
  3. 筛选条件重置:当表格的筛选/搜索条件变化时,记得重置游标状态,重新加载第一页数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:41:04