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

使用React Table与Django实现16万行大表分页的最佳方案是什么?

我有一张包含16万行、20+列的表,目前我已经配置了自定义Django Rest Framework分页,可通过查询字符串设置每页条数和页码:
http://127.0.0.1:8000/api/ix/?page=2&page_size=100

返回的JSON格式如下:

{
    "count": 166710,
    "next": "http://127.0.0.1:8000/api/ix/?page=3&page_size=100",
    "previous": "http://127.0.0.1:8000/api/ix/?page_size=100",
    "results": [{...}]
}

前端我打算用React Query拉取JSON数据,用React Table展示并处理分页逻辑。
从React Table的官方示例来看,它是拉取全量未分页数据后,在客户端做切片处理:

const startRow = pageSize * pageIndex
const endRow = startRow + pageSize
setData(serverData.slice(startRow, endRow))

可选方案

  • 关闭服务端分页,按示例在客户端做数据切片
    • 优势:只需少量修改示例代码即可实现
    • 劣势:首次数据加载耗时长,每次分页操作都可能触发数据重加载
  • 保留服务端自定义分页,自行实现分页逻辑对接
    • 优势:数据已在服务端分页,加载效率更高
    • 劣势:需要编写更多代码,易出问题,同时需遵循React单向数据流规则重新设计组件
  • 同时使用服务端分页+客户端分页
    • 该方案理论上最优,但如果服务端返回固定页长的分页数据,我不知道该如何实现。假设我无法再通过page_size参数查询,当用户修改每页条数时,客户端该如何拉取数据?比如我的API每页返回100条数据,现在用户要展示500条,React Table会“自动”多次调用API直到凑够500条数据吗?

请问哪种方案是最优选择?

我的实现方案

最终我通过服务端分页结合多个useEffect钩子将页码、每页条数同步到URL查询参数实现了需求,对接的是同样为服务端分页的Pokemon API,和我的场景非常相似,以下是实现代码:

import React from "react";
import { useTable, usePagination } from "react-table";
import { useQuery } from "react-query";
import { fetchIXData, ColName } from "fetchData";

const fetchIXData = async (page, pageSize) => {
    try {
      const response = await fetch(
        `http://127.0.0.1:8000/api/ix/?page=${page + 1}&page_size=${pageSize}`
      );   // 我的API是1起始索引,React Table是0起始索引,所以+1
      const data = await response.json();
      return data;
    } catch (e) {
      throw new Error(`API error: !{e?.message}`);
    }
  };
  
  const ColName = {
    col: "col1",
    // 其余19个列配置
};
  
const columns = Object.keys(ColName).map((key) => {
  return {
    Header: ColName[key],
    accessor: key,
  };
});

const initialState = {
  queryPageIndex: 0,
  queryPageSize: 20,
  totalCount: null,
};
const PAGE_CHANGED = "PAGE_CHANGED";
const PAGE_SIZE_CHANGED = "PAGE_SIZE_CHANGED";
const TOTAL_COUNT_CHANGED = "TOTAL_COUNT_CHANGED";

const reducer = (state, { type, payload }) => {
  switch (type) {
    case PAGE_CHANGED:
      return {
        ...state,
        queryPageIndex: payload,
      };
    case PAGE_SIZE_CHANGED:
      return {
        ...state,
        queryPageSize: payload,
      };
    case TOTAL_COUNT_CHANGED:
      return {
        ...state,
        totalCount: payload,
      };
    default:
      throw new Error(`未处理的动作类型: ${type}`);
  }
};

function Table() {
  const [{ queryPageIndex, queryPageSize, totalCount }, dispatch] =
    React.useReducer(reducer, initialState);

  const { isLoading, error, data, isSuccess } = useQuery(
    ["ixData", queryPageIndex, queryPageSize],
    () => fetchIXData(queryPageIndex, queryPageSize),
    {
      keepPreviousData: true,
      staleTime: Infinity,
    }
  );

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    prepareRow,
    page,
    canPreviousPage,
    canNextPage,
    pageOptions,
    pageCount,
    gotoPage,
    nextPage,
    previousPage,
    setPageSize,
    // 从实例获取状态
    state: { pageIndex, pageSize },
  } = useTable(
    {
      columns,
      data: isSuccess ? data.results : [],
      initialState: {
        pageIndex: queryPageIndex,
        pageSize: queryPageSize,
      },
      manualPagination: true,
      pageCount: isSuccess ? Math.ceil(totalCount / queryPageSize) : null,
    },
    usePagination
  );

  React.useEffect(() => {
    dispatch({ type: PAGE_CHANGED, payload: pageIndex });
  }, [pageIndex]);

  React.useEffect(() => {
    dispatch({ type: PAGE_SIZE_CHANGED, payload: pageSize });
    gotoPage(1);
  }, [pageSize, gotoPage]);

  React.useEffect(() => {
    if (data?.count) {
      dispatch({
        type: TOTAL_COUNT_CHANGED,
        payload: data.count,
      });
    }
  }, [data?.count]);

  if (error) {
    return <p>加载出错</p>;
  }

  if (isLoading) {
    return <p>加载中...</p>;
  }
  return (
    <>
      {isSuccess ? (
        <>
        <div className="pagination">
            <button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
              {"<<"}
            </button> 
            <button onClick={() => previousPage()} disabled={!canPreviousPage}>
              {"<"}
            </button> 
            <button onClick={() => nextPage()} disabled={!canNextPage}>
              {">"}
            </button> 
            <button
              onClick={() => gotoPage(pageCount - 1)}
              disabled={!canNextPage}
            >
              {">>"}
            </button> 
            <span>
              第 
              <strong>
                {pageIndex + 1} / {pageOptions.length}
              </strong> 页
            </span>
            <span>
              | 跳转到: 
              <input
                type="number"
                onChange={(e) => {
                  const page = Number(e.target.value) - 1;
                  if (e.target.value != null) gotoPage(page);
                }}
                style={{ width: "100px" }}
              />
            </span> 
            <select
              value={pageSize}
              onChange={(e) => {
                setPageSize(Number(e.target.value));
              }}
            >
              {[20, 30, 40, 50].map((pageSize) => (
                <option key={pageSize} value={pageSize}>
                  每页显示{pageSize}条
                </option>
              ))}
            </select>
          </div>


          <table {...getTableProps()}>
            <thead>
              {headerGroups.map((headerGroup) => (
                <tr {...headerGroup.getHeaderGroupProps()}>
                  {headerGroup.headers.map((column) => (
                    <th {...column.getHeaderProps()}>
                      {column.render("Header")}
                    </th>
                  ))}
                </tr>
              ))}
            </thead>
            <tbody {...getTableBodyProps()}>
              {page.map((row) => {
                prepareRow(row);
                return (
                  <tr {...row.getRowProps()}>
                    {row.cells.map((cell) => (
                      <td {...cell.getCellProps()}>{cell.render("Cell")}</td>
                    ))}
                  </tr>
                );
              })}
            </tbody>
          </table>
          
        </>
      ) : null}
    </>
  );
}

export default Table;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 10:27:04