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

如何在React+TypeScript项目中基于GraphQL实现高效分页?

基于TypeScript + Apollo Client的React分页最佳实践

1. 解决数据加载延迟,提升用户体验

用Apollo Client的useInfiniteQuery替代useQuery,它专门适配无限分页场景,支持自动跟踪分页状态,还能实现预加载(提前触发下一页请求,减少用户等待感)。

优先采用Cursor分页(比Offset更高效)

如果服务器支持,优先用Cursor分页(Offset分页在数据量大时易出现性能问题和数据重复/遗漏),调整GraphQL查询:

query GetItems($cursor: String, $limit: Int) {
  items(cursor: $cursor, limit: $limit) {
    edges {
      node {
        id
        name
        description
      }
    }
    pageInfo {
      endCursor
      hasNextPage
    }
  }
}

若只能用Offset分页,也可适配useInfiniteQuery,但Cursor是更优方案。

组件中实现无限滚动+预加载

import { useInfiniteQuery } from '@apollo/client';
import { GET_ITEMS } from './queries';

type Item = {
  id: string;
  name: string;
  description: string;
};

type ItemsQueryResult = {
  items: {
    edges: Array<{ node: Item }>;
    pageInfo: { endCursor: string; hasNextPage: boolean };
  };
};

const ItemsList = () => {
  const {
    data,
    loading,
    error,
    fetchNextPage,
    hasNextPage,
  } = useInfiniteQuery<ItemsQueryResult>(GET_ITEMS, {
    variables: { limit: 10 },
    // 自动计算下一页参数
    getNextPageParam: (lastPage) => {
      return lastPage.items.pageInfo.hasNextPage ? lastPage.items.pageInfo.endCursor : undefined;
    },
    // 滚动到底部自动加载下一页
    onSuccess: () => {
      const observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && hasNextPage) {
          fetchNextPage();
        }
      });
      observer.observe(document.getElementById('load-more-trigger')!);
    },
  });

  // 合并所有分页数据
  const allItems = data?.pages.flatMap(page => page.items.edges.map(edge => edge.node)) || [];

  if (loading) return <div>加载中...</div>;
  if (error) return <div>加载失败</div>;

  return (
    <div>
      {allItems.map(item => (
        <div key={item.id}>
          <h3>{item.name}</h3>
          <p>{item.description}</p>
        </div>
      ))}
      {hasNextPage && <div id="load-more-trigger">加载更多...</div>}
    </div>
  );
};

2. 优化缓存,避免重复请求

Apollo Client默认缓存查询结果,但分页场景需手动配置缓存合并规则,确保数据不重复且正确累加:

针对Cursor分页的缓存配置

在Apollo Client初始化时添加类型策略:

import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: '/graphql',
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          items: {
            // 以limit为缓存分组键,cursor作为分页区分参数
            keyArgs: ['limit'],
            // 合并新数据到现有缓存,自动去重
            merge(existing, incoming) {
              let edges = existing?.edges || [];
              if (incoming?.edges) {
                const existingIds = new Set(edges.map(edge => edge.node.id));
                edges = [...edges, ...incoming.edges.filter(edge => !existingIds.has(edge.node.id))];
              }
              return {
                ...incoming,
                edges,
              };
            },
          },
        },
      },
    },
  }),
});

Offset分页的缓存处理

若使用Offset分页,可通过fetchPolicy: 'cache-first'优先读取缓存,同时手动维护offset状态,避免重复请求相同offset的数据:

const { data } = useQuery(GET_ITEMS, {
  variables: { offset, limit: 10 },
  fetchPolicy: 'cache-first',
});

3. 保证分页与缓存的类型安全

手动定义TypeScript类型易出错,推荐用GraphQL Code Generator自动生成所有查询、变量、返回值的类型:

  1. 安装依赖:
    npm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-apollo-client @graphql-codegen/typescript-operations
    
  2. 配置codegen.ts:
    import type { CodegenConfig } from '@graphql-codegen/cli';
    
    const config: CodegenConfig = {
      schema: 'http://your-graphql-server-url',
      documents: './src/**/*.graphql',
      generates: {
        './src/generated/graphql.ts': {
          plugins: ['typescript', 'typescript-operations', 'typescript-apollo-client'],
        },
      },
    };
    
    export default config;
    
  3. 添加npm脚本到package.json:
    {
      "scripts": {
        "codegen": "graphql-codegen --config codegen.ts"
      }
    }
    
  4. 运行npm run codegen生成类型,之后在组件中直接导入使用:
    import { useInfiniteQuery } from '@apollo/client';
    import { GetItemsDocument, GetItemsQuery, GetItemsQueryVariables } from './generated/graphql';
    
    const ItemsList = () => {
      const { data, loading, error } = useInfiniteQuery<GetItemsQuery, GetItemsQueryVariables>(
        GetItemsDocument,
        { variables: { limit: 10 } }
      );
    
      // 所有类型自动匹配,不会出现类型不匹配问题
      const allItems = data?.pages.flatMap(page => page.items?.edges?.map(edge => edge.node) || []) || [];
      // ... 渲染逻辑
    };
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 16:14:57