antd Pagination 受控模式完全指南:受控页码的用法、核心 API 与源码实现解析
2026/9/19 9:30:37 网站建设 项目流程

antd Pagination 受控模式完全指南:受控页码的用法、核心 API 与源码实现解析

【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design

导读

controlled.md是 antd Pagination 组件的“受控”示例文档,主题虽短,却对应着分页组件最重要的一种使用形态:由开发者完全接管当前页码的状态。本文以该演示为骨架,完整还原受控页码的写法,并结合组件源码与测试用例,讲透currentdefaultCurrentonChange等核心 API 的语义,以及受控模式在服务端分页场景中的实战落地方案。读完你将能够熟练编写、调试和扩展受控分页,并理解其底层状态流转机制。

一、什么是受控分页:演示代码逐行拆解

受控(Controlled)组件的核心特征是:组件的展示状态由外部props决定,任何交互变更都必须通过回调反馈给外部,由外部决定是否更新状态。Pagination 的受控模式即“受控制的页码”。

仓库中的演示文件 controlled.tsx 给出了完整的最小实现:

import React, { useState } from 'react'; import type { PaginationProps } from 'antd'; import { Pagination } from 'antd'; const App: React.FC = () => { const [current, setCurrent] = useState(3); const onChange: PaginationProps['onChange'] = (page) => { console.log(page); setCurrent(page); }; return <Pagination current={current} onChange={onChange} total={50} />; }; export default App;

对照文档注释(controlled.md)——“受控制的页码 / Controlled page number”,这段代码的要点可拆解为三部分:

  1. 状态上移:页码状态current通过useState(3)声明在组件外部(父组件),初始值为第 3 页。演示刻意将初始值设为非 1,用于直观体现“外部决定初始页码”的能力。
  2. 双向绑定<Pagination current={current} onChange={onChange} />current把当前页码“喂”给组件;用户点击页码、上一页/下一页、快速跳转(Quick Jumper)时触发onChange,回调参数page即用户想要跳转的新页码。
  3. 状态回流onChange内部调用setCurrent(page),把新页码写回 React 状态,触发重渲染,Pagination的选中项随之更新。由此形成一个完整闭环:UI 交互 → onChange 回调 → 外部 setState → props 回流 → UI 更新

对应文档 controlled.md 只用了两句话,但其背后是整个受控组件设计模式在分页场景的落地,也是服务端分页(后端返回数据、前端受控翻页)的标准写法。

二、受控 vs 非受控:从 API 语义看状态归属

要真正掌握受控模式,需要先分清 Pagination 的两套状态入口。官方 API 表(index.en-US.md)中与本主题直接相关的参数如下:

参数说明类型默认值
current当前页码number-
defaultCurrent默认的初始页码number1
pageSize每页条数number-
defaultPageSize默认的每页条数number10
total数据总条数number0
onChange页码或pageSize改变时触发,参数为新的页码与每页条数function(page, pageSize)-
onShowSizeChangepageSize改变时触发function(current, size)-

核心区分规则:

  • 非受控(默认形态):不传current,只传defaultCurrent(默认 1)。页码状态由 Pagination 内部维护,用户点击后组件自己更新高亮页,onChange仅作为“通知”告知外部发生了翻页。典型例子见 basic.tsx:

    const App: React.FC = () => <Pagination defaultCurrent={1} total={50} />;
  • 受控(本演示形态):传入current,页码状态完全由外部掌控。用户点击后组件不会自行决定最终选中页,而是把意图通过onChange抛给外部——若外部不调用setState,页码会“弹回”原值。这就是受控组件的经典约束:外部不更新,UI 不变化

从源码结构看,Pagination.tsx 的PaginationProps接口直接继承自rc-paginationRcPaginationProps(第 7、20 行),currentdefaultCurrentonChange等属性都定义于底层rc-pagination,antd 组件本身不持有页码状态,而是把剩余 props 原样透传给RcPagination(第 133-143 行):

<RcPagination {...iconsProps} {...restProps} style={mergedStyle} prefixCls={prefixCls} selectPrefixCls={selectPrefixCls} ... />

这意味着:受控与不受控的判别逻辑、页码高亮计算都发生在rc-pagination内部,antd 层负责的是前缀类名、图标(左右箭头、省略号)、尺寸适配、RTL 与样式变量等上层封装。

三、受控模式下 onChange 与 onShowSizeChange 的行为细节

受控模式下有两个回调需要重点关注,测试用例 index.test.tsx 给出了可验证的预期行为:

it('should onChange called when pageSize change', () => { const onChange = jest.fn(); const onShowSizeChange = jest.fn(); const { container } = render( <Pagination defaultCurrent={1} total={500} onChange={onChange} onShowSizeChange={onShowSizeChange} />, ); fireEvent.mouseDown(container.querySelector('.ant-select-selector')!); expect(container.querySelectorAll('.ant-select-item-option').length).toBe(4); fireEvent.click(container.querySelectorAll('.ant-select-item-option')[1]); expect(onChange).toHaveBeenCalledWith(1, 20); });

该测试确认了两个事实:

  1. onChange的第二个参数是pageSize:当用户通过sizeChanger切换每页条数时,onChange同样会被触发,且签名固定为(page, pageSize)。所以在受控模式中,onChange是“页码或 pageSize 任一变化”的统一出口,建议在回调里同时更新currentpageSize两个状态,保证受控数据一致:

    const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const onChange: PaginationProps['onChange'] = (nextPage, nextPageSize) => { setPage(nextPage); setPageSize(nextPageSize); }; return ( <Pagination current={page} pageSize={pageSize} total={500} showSizeChanger onChange={onChange} /> );
  2. onShowSizeChange是独立于onChange的钩子:它只感知“每页条数变化”,签名是(current, size)。当业务需要区分“翻页”与“改每页条数”两种动作(例如只在大小时重置页码)时,可以同时使用两者。

另外注意官方 API 中的默认行为:showSizeChanger默认在total > 50时为 true(源码第 61 行也展示了它与ConfigProvider中全局pagination.showSizeChanger的合并逻辑showSizeChanger ?? pagination.showSizeChanger)。

四、源码级原理:antd 如何透传与增强受控分页

从源码结构可以推断出受控分页的完整调用链:

  1. 用户传入current/onChange到 antd 的 Pagination.tsx(第 37 行起的函数组件)。
  2. antd 层从 props 中解构出与自身相关的alignprefixClsselectPrefixClssizeresponsiveshowSizeChanger等,其余通过...restProps(第 50 行)连同onChange等一起原样转发给RcPagination(第 133-143 行)。也就是说,受控逻辑本身不在 antd 代码内,antd 只做“增强转发”。
  3. rc-pagination内部根据props.current是否存在决定走受控还是非受控分支,并计算展示的页码列表(含省略号•••、上一页/下一页按钮等)。

antd 层做的增强工作包括:

  • 图标注入:根据direction(RTL/LTR)自动切换左右箭头方向(第 63-102 行),通过prevIconnextIconjumpPrevIconjumpNextIcon传入rc-pagination
  • 尺寸适配useBreakpoint(responsive)结合useSize,当size未指定且窗口为小屏(xs)时自动切换为ant-pagination-mini(第 52、110 行);
  • 选择器替换:用 antd 自身的MiddleSelect/MiniSelect替换rc-pagination默认的 pageSize 下拉(第 140 行);
  • 主题与样式:通过useStyle(prefixCls)注入 CSS-in-JS 变量与 hashId(第 59 行),wireframe 模式下额外渲染BorderedStyle(第 132 行)。

这些增强均不影响受控语义:current由外部持有这一约束,自始至终成立。

五、受控分页的典型实战场景:服务端分页

受控模式最常见的落地场景是服务端分页:数据不在前端一次性渲染,而是每次翻页向后端请求对应页的数据。核心写法规避了“页码显示”与“数据内容”不一致的时序问题:

import React, { useState, useEffect } from 'react'; import type { PaginationProps } from 'antd'; import { Pagination, Table } from 'antd'; const App: React.FC = () => { const [current, setCurrent] = useState(1); const [pageSize, setPageSize] = useState(10); const [total, setTotal] = useState(0); const [data, setData] = useState<Record<string, unknown>[]>([]); useEffect(() => { // 模拟向后端请求:受控页码/每页条数直接作为查询参数 fetch(`/api/list?page=${current}&pageSize=${pageSize}`) .then((res) => res.json()) .then((res) => { setData(res.list); setTotal(res.total); }); }, [current, pageSize]); const onChange: PaginationProps['onChange'] = (page, size) => { setCurrent(page); setPageSize(size); }; return ( <> <Table rowKey="id" dataSource={data} pagination={false} /> <Pagination current={current} pageSize={pageSize} total={total} showSizeChanger showQuickJumper showTotal={(t, range) => `${range[0]}-${range[1]} of ${t} items`} onChange={onChange} /> </> ); }; export default App;

关键点在于:受控状态下,Pagination 只负责“表达”current,而“真正翻页取数”由useEffect依据[current, pageSize]驱动。快速连续点击时,React 会以最后一次状态为准发起请求,天然规避了非受控模式下“UI 已跳页但数据未更新”的中间态。

若需求是“每页条数变化时页码重置回 1”,可在onChange中判断:当size !== pageSize时,把page一并重置:

const onChange: PaginationProps['onChange'] = (page, size) => { setPageSize(size); setCurrent(size === pageSize ? page : 1); };

六、测试保障:演示与受控行为的自动化验证

仓库为演示代码提供了两层自动化保障,可放心照抄controlled.tsx的写法:

  • 演示快照测试:demo.test.ts 调用共享工具demoTest('pagination'),它会遍历 tests/shared/demoTest.tsx 中globSync('./components/${component}/demo/*.tsx')匹配到的全部演示文件,逐个 SSR 渲染并与快照比对。受控演示的渲染结果固化在 demo.test.ts.snap 中(renders components/pagination/demo/controlled.tsx correctly),保证演示代码可稳定渲染。
  • 交互行为测试:上文引用的 index.test.tsx 覆盖了onChange参数签名、pageSize 切换触发行为、RTL 渲染、ConfigProvider尺寸继承、align对齐等,是受控模式下回调语义的直接证据。

七、受控模式常见误区与规避建议

  1. 只传current不处理onChange:此时用户点击看似“无效”(页码弹回),容易误判为 Bug。受控组件必须配套状态回流,要么实现onChange更新外部状态,要么改用非受控的defaultCurrent
  2. 混用currentdefaultCurrent:受控与非受控入口二选一。传入current后,defaultCurrent只在初始化时兜底生效,后续完全以current为准,混用会造成心智混乱。
  3. 忘记更新pageSize:受控模式下onChange会携带新的pageSize(见第三节测试证据),若只setPagesetPageSize,翻页后“每页条数”选择器可能与实际数据条数不一致。
  4. 异步取数时竞态:服务端分页下翻页过快时,较早的请求可能后返回并覆盖新数据。可引入请求序号或取消机制,确保以最后一次请求为准。

小结

受控分页的本质是把“当前页码”这一状态从组件内部提升到业务层,通过current+onChange建立闭环。从仓库证据看,antd 的 Pagination.tsx 不参与状态持有,只负责透传与增强,受控语义由底层rc-pagination保证;index.test.tsx 则固化了onChange(page, pageSize)的调用约定。掌握 controlled.tsx 展示的最小闭环,再配合pageSizeonShowSizeChange与异步取数,即可在服务端分页等真实业务中稳定驾驭受控 Pagination。

【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询