ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

antd Table rowSelection 多选状态与跨页实战

antd Table rowSelection 多选状态与跨页实战 1. 先搞清楚 rowSelection 到底替我们做了什么antd 的 Table 组件里rowSelection这个属性大概是使用频率排前几的一个配置项。只要你的业务里出现了列表批量操作——批量删除、批量导出、批量改状态、批量分配权限——那基本都绕不开它。我先说清楚它能干什么给表格每一行前面加一个多选框checkbox维护一个当前选中了哪些行的集合并把集合变化通过回调抛给使用者。就这么个事看着简单但真到项目里回显、跨页、全选、禁用、性能一个接一个地冒出来。这篇东西适合谁看如果你刚开始用 antd 的 Table想快速把多选列表搭起来前面几节能直接抄如果你已经被取消勾选后页面没更新切换分页选中丢了上千行数据一勾就卡折磨过那第四章和第五章应该更对你胃口。我不打算照着官方文档复述参数表那种东西你打开文档就有我想讲的是这些参数背后为什么要这么设计、什么时候该用哪个、以及我在真实项目里踩过的坑。多选框checkbox本身是再基础不过的表单元素但一旦和表格 分页 服务端数据组合起来它的状态管理复杂度会指数级上升。核心矛盾就一个谁持有选中的真值。是你自己的 state还是 antd 内部想明白这一点后面所有问题都能顺着解。2. rowSelection 的核心机制与参数取舍2.1 selectedRowKeys 与 onChange 的配合逻辑rowSelection最关键的三个东西是selectedRowKeys、onChange、rowKey。它们三者的关系决定了整个多选行为是不是可控。先说rowKey。这是 Table 每行的唯一标识默认取record.key但真实业务数据里往往没有key字段于是很多人顺手写成rowKeyid。这本身没问题问题在于如果你不写 rowKey也不给数据加 keyantd 会打印警告并且选中状态会彻底错乱。因为组件内部是靠这个 key 去比对这一行是不是被选中的key 重复或者不稳定比如用了数组下标当 key勾选就会出现勾了 A 行B 行也亮了的诡异现象。这是我见过最高频的低级错误没有之一。再说受控模式。selectedRowKeys一旦传了就是受控不传就是非受控antd 自己维护内部状态。区别在哪非受控模式下你拿不到实时选中的行数据只能在onChange里通过第二个参数selectedRows拿到当前页选中的行。onChange的签名是(selectedRowKeys, selectedRows, info) void第三个参数info里有个type值可能是all或single用来区分这次变化是点了全选还是点了单行。很多人不知道有这个info结果在全选场景下写出了很绕的判断逻辑。我个人的习惯是只要涉及批量操作一律走受控。理由很实在——非受控下你没法在删除成功后清空选中状态也没法在数据刷新后回显历史选中一旦业务加了这些需求非受控模式就得推倒重写。const [selectedRowKeys, setSelectedRowKeys] useStateReact.Key[]([]); Table rowKeyid rowSelection{{ selectedRowKeys, onChange: (keys, rows, info) { setSelectedRowKeys(keys); }, }} dataSource{data} columns{columns} /上面这十几行就是最小可用版本但请注意一个隐蔽点setSelectedRowKeys(keys)里 keys 的类型是React.Key[]如果你后面用keys.includes(row.id)去判断而row.id是 number就会出现类型不一致导致判断永远为 false。解决办法是统一 todo 成 string或者统一成 number别混用。2.2 getCheckboxProps禁用某一行勾选的正确姿势getCheckboxProps返回的对象会直接透传给 checkbox所以你可以在这里设disabled、name等属性。最常见的需求是某些行不允许被选中比如已归档的记录、没有权限操作的记录。rowSelection{{ selectedRowKeys, onChange: setSelectedRowKeys, getCheckboxProps: (record) ({ disabled: record.status archived, }), }}这里有个我踩过的坑禁用行不会从 selectedRowKeys 里自动移除。假设用户先全选然后某几行因为状态变化被禁用了这时候 selectedRowKeys 里可能还残留着这些行的 key。你在提交批量操作时如果不做过滤服务端就会收到不该操作的 ID。稳妥做法是在提交前用当前 dataSource 过滤一遍const validKeys selectedRowKeys.filter((key) data.some((item) item.id key item.status ! archived) );另外getCheckboxProps在数据量大的时候会被频繁调用函数体里别放重计算否则滚动和勾选都会明显变卡。我见过有人在这里面做字符串拼接 正则匹配几百行数据一勾选整个页面卡住两秒排查了半天才发现是这个函数在背锅。2.3 preserveSelectedRowKeys跨页保持选中到底开不开antd 4.x 之后提供了preserveSelectedRowKeys打开它之后切换分页时勾选的 key 会保留在 selectedRowKeys 里不会因为数据源变化而丢失。听起来很美好但你要清楚它做了什么它只是让你的 keys 数组不被组件内部清理并不会帮你保留行数据。这意味着什么呢如果你在 onChange 里拿selectedRows跨页后这个数组里对应的行数据可能已经是空的了。所以真正要跨页选中的业务正确姿势是自己维护一份已选行的完整数据的 Mapkey 做索引onChange 时增量更新而不是依赖selectedRows。跨页全选还有个语义问题值得掰扯当前页全选和全部数据全选是两码事。antd 的全选只针对当前 dataSource也就是当前页。如果你要做勾选后对全部符合条件的记录生效那就不能只靠 rowSelection 了得配合服务端的全选标记比如给后端传一个selectAll: true加上筛选条件让后端去处理。前端硬扛全量勾选数据量上万时直接把浏览器内存干爆。3. 从零搭一个可落地的可选择列表3.1 数据结构与 rowKey 设计的先后顺序动手之前先把三件事定下来顺序不能反。第一确定唯一键。服务端返回的数据里一定有一个稳定不变的主键哪怕字段名叫uuid或者code都比用数组索引靠谱。永远不要用 index 当 rowKey这是铁律。第二确定选中集合的类型。我建议统一用string[]。原因很朴素URL 参数、localStorage、接口传参最终都是字符串形态提前统一能省掉后面到处String()的麻烦。第三确定选中态存在哪。如果只是当前组件用useState就够如果要跨组件、跨路由共享比如列表页勾选后跳到另一个页面提交那就得上状态管理或者 Context别硬扛。把这三点定完代码其实就成功一半了。我见过太多项目是先写页面、后发现要跨页、再回头改 key 和 state 结构返工成本极高。设计先行这句话在多选列表上体现得特别明显。3.2 基础版实现勾选 批量操作条下面是一个可以直接拿去改的完整骨架包含批量删除和选中数量展示。import { useState } from react; import { Table, Button, Space, Popconfirm, message } from antd; export default function UserList() { const [data, setData] useState(initialData); const [selectedRowKeys, setSelectedRowKeys] useStateReact.Key[]([]); const columns [ { title: 用户名, dataIndex: name }, { title: 角色, dataIndex: role }, { title: 状态, dataIndex: status }, ]; const handleBatchDelete async () { await api.batchDelete(selectedRowKeys); setData((prev) prev.filter((item) !selectedRowKeys.includes(item.id))); setSelectedRowKeys([]); message.success(删除成功); }; return ( Space directionvertical style{{ width: 100% }} Space span已选 {selectedRowKeys.length} 项/span Popconfirm title确认删除选中的记录 onConfirm{handleBatchDelete} Button danger disabled{!selectedRowKeys.length} 批量删除 /Button /Popconfirm /Space Table rowKeyid rowSelection{{ selectedRowKeys, onChange: setSelectedRowKeys }} columns{columns} dataSource{data} / /Space ); }注意几个细节删除成功后要setSelectedRowKeys([])清空选中否则页面会残留选中状态按钮在没选中时要disabled避免用户点了个寂寞用 Popconfirm 二次确认是批量操作的标配别省。这里补一句操作心得批量删除接口返回后不要简单地把 data 里对应项删掉就完事最好重新拉一次列表。原因在于分页数据是会错位的——你删掉当前页三条后端第二页的第一条会补上来前端如果只做本地删除页码和数据就对不上了。当然如果数据量很小全量返回本地删除也没问题看场景取舍。3.3 全选 / 半选状态的视觉与逻辑处理antd 的全选框自带半选indeterminate状态当选中数量大于 0 且小于当前页总数时表头复选框会显示一条横线而不是对勾。这个行为是组件内建的你不用自己算但有个边界要注意——如果你手动设置了hideSelectAll: true隐藏全选框那半选逻辑也就没了。跨页场景下半选状态的计算只针对当前页。也就是说你在第一页全选翻到第二页表头是全不选的状态这是符合直觉的。但如果你开了preserveSelectedRowKeys用户可能会困惑我明明全选了怎么第二页没勾上这时候通常的做法是在操作条上提示已选 N 项让用户清楚总数而不是纠结表头的视觉状态。还有一种进阶做法把表头全选框替换成自定义的下拉菜单用columnTitle或者onHeaderCell定制提供选中本页选中全部两个选项。这个实现起来稍微绕需要自己接管全选逻辑好处是语义清晰适合后台管理系统这种对批量操作要求高的场景。4. 常见问题与排查实录4.1 勾选没反应、回显丢失的排查顺序遇到点了复选框但选中状态不更新按下面顺序查基本能命中。排查项典型症状解决方式rowKey 未设置或重复勾选串行、随机亮指定稳定唯一主键selectedRowKeys 类型不一致传了 keys 但无勾选统一 string 或 numberonChange 未回写 state点击后无任何反应确认 onChange 里调用了 setState数据源每页重复 key翻页后选中错乱检查后端返回是否有重复 ID受控与非受控混用初始有值后面失控全程受控不在中途切换我印象最深的一次排查列表用的是id作为 rowKey但后端在某个接口里返回的id是 number另一个接口返回的是 string导致切换数据源后回显失败。这种问题用日志打一下typeof record.id就能定位但如果不往类型上想很容易在组件逻辑里绕圈。4.2 与分页、筛选、排序联动时的键值陷阱当表格开启分页勾选和分页的关系必须想清楚。默认情况下切换分页时 antd 会根据新的 dataSource 计算选中态selectedRowKeys 里那些不在当前页的 key 不会显示勾选这是对的但它们还在数组里。如果你没开preserveSelectedRowKeys在某些版本里切换数据源时这些 key 可能会被清掉导致你切回来发现选中没了。筛选和排序同理。筛选后当前页数据变了但要保证选中集合语义明确是选中了这些具体的记录还是选中了符合某条件的所有记录前者用 keys 数组就够了后者必须走后端。我强烈建议在 UI 上把这两者区分开否则用户根本不知道自己删的是什么。还有一个容易被忽略的点服务端分页时如果你用 keys 去接口查完整数据做导出记得分批传参或者用 POST body别把几百个 ID 拼在 URL 上会超长被截断表现为部分记录导出丢失坑得很隐蔽。4.3 数据量大时勾选卡顿的性能处理上千行数据勾选卡顿原因通常有三个。第一rowKey不稳定导致 antd 每次都做全量 diff。第二onChange里做了重计算比如每次都遍历整份数据。第三getCheckboxProps里逻辑太重或者返回了一个每次新建的对象导致子组件无谓重渲染。优化方向上首先确保getCheckboxProps返回逻辑足够轻理想情况下只读 record 上的一个布尔字段。其次如果开启了虚拟滚动antd 4.x 的virtual属性要注意它和 rowSelection 的兼容性部分版本下虚拟滚动 多选会有滚动时的勾选错位建议升级到较新版本再使用。一个实用技巧把选中数量这种高频变化的展示抽到独立小组件里用React.memo包一层避免每次勾选都触发整个表格重新渲染。实测在两千行左右的场景下这个小改动能把单次勾选的响应从肉眼可见的迟滞降到基本无感。另外如果业务允许优先考虑选择模式而不是常驻勾选框。也就是说平时表格没有多选框用户点击批量操作按钮后才进入选择模式选完退出。这样既减少视觉噪音也能在选择模式下再做针对性优化体验比常驻勾选好很多。这个交互模式在主流后台系统里越来越常见值得借鉴。4.4 一个高频报错的快速定位思路最后啰嗦一句排查方法。多选相关的 bug十有八九出在key上。当你遇到任何诡异现象——勾选错位、回显失败、切换分页丢选中、删除后状态残留——第一件事就是打开控制台打印selectedRowKeys和当前 dataSource 的 id 列表对照着看类型和值是否匹配。我通常会写这么一个临时调试console.log(keys:, selectedRowKeys, selectedRowKeys.map((k) typeof k)); console.log(data ids:, data.map((d) ${d.id}(${typeof d.id})));一眼就能看出是类型问题还是值对不上。很多同学遇到问题就去翻组件文档、搜 issue其实把这两行打印出来五分钟定位比翻半小时资料管用。如果确认 key 没问题再看受控状态是不是被别的逻辑覆盖了——比如父组件传下来的 props 有变化、或者你在 useEffect 里做了重置。多选问题的排查思路就一条盯住真值的来源确认它没有被意外改写。5. 我踩过的坑和几条实战建议说几个文档里不会写、但实际项目里特别容易翻车的点。第一批量操作成功后清空选中这个顺序别搞反。要先把数据更新完再清空 selectedRowKeys。如果先清空某些框架下会触发一次空数组的渲染虽然结果一样但可能出现闪烁。第二selectedRows不要跨渲染周期使用。它是 onChange 那一刻的快照你把它存到 state 里当已选数据源等用户切换分页后再用很可能拿到过期的对象引用。要用就用 keys 重新查。第三二次确认弹窗里显示的数量要和实际提交的数量一致。我见过一个 bug弹窗文案写删除 5 条实际提交了 8 条原因是半选状态下的父节点也被算进去了。这种问题一旦发生在生产环境用户投诉会很直接。第四给多选加个取消选择的显式入口。用户勾了一堆之后想清空如果只能一个个点掉体验很差。一个清空选择的小按钮成本极低好评度很高。第五如果你的列表要和后端的分页、筛选、排序完全对齐建议把选中状态和查询条件一起管理甚至可以考虑把选中 key 同步到 URL 上用 query 参数这样用户刷新页面还能恢复选中虽然 URL 会变长但在审批、对账这类场景下很实用。自定义内容后续还能这么扩展把 rowSelection 和操作日志结合记录每次批量操作的选中项或者做选择模板把常用的筛选 选中组合保存下来一键复用。这些都是在基础实现稳定之后往上叠加的价值点先把地基打牢再说。
返回列表