ARTICLE DETAIL

资讯详情

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

el-table多选全解析:获取选中行、跨页保留与回显实战

el-table多选全解析:获取选中行、跨页保留与回显实战 做后台管理系统十有八九逃不开一张带多选的表格。el-table 的多选列看着很省事一个typeselection就能出全选和行选择但真正写业务的时候很多人还是会在“怎么把选中的行拿到自己手里”这一步卡住。我这两年用 vue 配合 element-ui / element-plus 做了不少管理后台这个功能几乎每个项目都要写一遍而且每次写都会碰到不同的问题今天干脆一次性把它讲透。这篇文章主要围绕三件事展开el-table 多选背后的工作原理、获取选中行的几种正确姿势、以及跨页选中、默认回显、全选联动这些高频进阶场景。无论你是刚开始写 vue 的新手还是已经做了几个项目但一直被 selection-change 折腾的老手都能在里头找到用得上的东西。1. el-table 多选的基本玩法先一次性看透1.1 typeselection 这列到底做了什么你说它神奇其实原理很简单。el-table 渲染出一列typeselection的列之后Element 会自动在表头生成一个复选框在每一行生成一个复选框。表头那个复选框负责控制当前所有行的全选与取消行复选框则负责控制对应行的选中状态。重点是这些选中状态是存在 el-table 组件内部的由组件自己维护不是绑定在你 data 里的。你之所以能在 selection-change 回调里拿到选中的行数组是因为组件在内部状态发生变化后把这个数组作为参数抛了出来。这也解释了为什么很多新人把选中状态存到自己定义的变量里页面一刷新又没了——它原本就不属于你的数据层只是被临时传给了你你不存就接不住。另一个容易忽略的点行复选框的选中本质上依赖的是行数据对象的引用。el-table 判断“这一行有没有被选中”会在内部维护一个选中的行对象集合。当你在业务里对表格数据做 filter、map 等操作生成了新的对象引用原来的选中关系就会对不上。这个坑发生在“表格数据由接口返回前端又做了二次处理”的场景里后面排查章节我会专门讲。1.2 三个逃不掉的方法toggleRowSelection、toggleAllSelection、clearSelection这三个方法是 el-table 对外暴露的核心控制方法通过 ref 拿到组件实例后调用toggleRowSelection(row, selected)切换指定行的选中状态第二个参数不传就自动取反。toggleAllSelection()切换当前页所有行的全选状态。clearSelection()清空所有行的选中状态。为什么需要这几个方法因为它们能让你在组件外部控制组件内部的选中状态。你往往会遇到这种场景数据加载完成后默认勾选几行用户点按钮后清空选中或者把某一行的选中状态同步给另一个组件。这些都需要借助 ref 拿实例来操作。很多面试官也喜欢问这个点el-table 的选中行怎么获取、怎么清空。本质上就是问 selection-change 和 clearSelection 的用法但如果你能把“选中状态由组件内部维护”这个原理讲清楚回答的深度完全不一样。这跟那种背 API 表面用法的答案差别是很大的。2. 获取选中行的三条路我推荐你走第一条2.1 事件驱动selection-change 被动接收选中行这是最常用、也最推荐的方式。在 el-table 上监听selection-change事件回调参数就是当前选中的行数组。el-table selection-changehandleSelectionChange el-table-column typeselection width55 / /el-tablehandleSelectionChange(rows) { this.selectedRows rows }为什么首推这个方案因为 el-table 在每次选中状态变化时都会触发这个事件而且参数就是最新的选中行数组。你只需要在事件回调里把它存到一个响应式变量里之后按钮组件、选中条数统计、批量操作统统可以直接用。需要注意的点回调确实很频繁。每次点一个复选框就会触发一次所以不要在回调里做复杂的操作比如发请求、做大量计算。把数据存下来就好具体业务放到按钮点击事件里做。我之前有个同事在 selection-change 里直接调了统计接口结果用户多选几行就发出去好几次请求后端都给打懵了。2.2 主动读取$refs 拿当前 selection第二种方式是在需要用到数据的时候直接通过组件实例去拿const selectedRows this.$refs.multipleTable.selection使用场景很明确只在点击按钮的瞬间才需要拿到选中行平时不关心。这种做法的好处是省去一个响应式变量的维护也不用在事件回调里做任何事。不过要注意一个细节this.$refs.multipleTable.selection拿到的数据在选中状态变化之后是同步更新的。也就是说你点击行复选框、触发 selection-change 之后再点批量操作按钮通过 refs 读取到的数据是准确的。这个方案唯一别扭的地方在于模板里没有办法直接响应式地根据选中数量去切换按钮的禁用状态因为你不是通过响应式数据去驱动 UI 的。如果你需要“按钮没选中时禁用”这种交互建议还是走 2.1 的方案。2.3 手动维护用 toggleRowSelection 掌握主动权第三种方式适合高阶场景。当你不满足于默认的全选/多选逻辑想在某个业务节点精确控制哪些行选中、哪些行取消时就要借助toggleRowSelection。// 选中某一行 this.$refs.multipleTable.toggleRowSelection(row, true) // 取消某一行 this.$refs.multipleTable.toggleRowSelection(row, false) // 切换某一行 this.$refs.multipleTable.toggleRowSelection(row)配合手动维护一个选中集合可以让业务逻辑变得更可控。比如我用 Set 来存选中的 idconst selectedIdSet new Set() handleSelectionChange(rows) { selectedIdSet.clear() rows.forEach(row selectedIdSet.add(row.id)) } // 想恢复某个选中状态时 const row this.tableData.find(item item.id targetId) this.$refs.multipleTable.toggleRowSelection(row, true)有的同学会问这个方案跟 2.1 有什么区别区别在于 2.1 是在“接收结果”3 是在“主动操作”。跨页保留、复杂回显、部分过滤后全选这些场景靠被动接收完全做不了必须主动给组件下指令。这也是为什么几个方案不是互斥关系而是配合使用的关系。我实际项目里经常是 2.1 维护状态 2.3 做回显控制两个一起用。3. 实战演示搞定一个带批量删除和导出的表格页3.1 需求与结构拆解按钮栏为什么要放这里假设我们要做的是一个用户列表页需求是支持勾选行、点击批量删除按钮删除选中数据、点击批量导出按钮导出选中数据。按钮栏放在表格上方实时显示当前选中条数没有选中时按钮置灰。为什么要这么设计按钮禁用避免了用户无意义的操作也减少后端压力显示条数给用户明确反馈让他知道自己的操作会作用于多少条数据删除是批量操作必须弹框二次确认。导出则可以相对轻量直接触发接口。这个页面的核心交互链路是勾选行 → selection-change 记录选中数组 → 按钮状态跟随变化 → 点击按钮读取选中数组 → 调接口 → 成功后清空选中并刷新表格。把这条链路走通整个页面的骨架就立住了。3.2 Vue 3 Element Plus 完整实现现在很多新项目都在用 Vue 3 和 Element Plus我先把这套完整的实现贴出来。template div classtable-page div classtoolbar el-button typedanger :disabled!selectedRows.length clickhandleBatchDelete 批量删除{{ selectedRows.length }} /el-button el-button :disabled!selectedRows.length clickhandleExport 批量导出 /el-button /div el-table reftableRef :datatableData row-keyid selection-changehandleSelectionChange el-table-column typeselection width55 / el-table-column propname label姓名 / el-table-column propphone label手机号 / el-table-column propcreateTime label创建时间 / /el-table /div /template script setup import { ref, onMounted } from vue import { ElMessage, ElMessageBox } from element-plus const tableRef ref(null) const tableData ref([]) const selectedRows ref([]) const loadData async () { // 这里换成你的列表接口 const res await fetchUserList() tableData.value res.data } const handleSelectionChange (rows) { selectedRows.value rows } const handleBatchDelete async () { await ElMessageBox.confirm( 确定删除选中的 ${selectedRows.value.length} 条数据吗, 提示, { type: warning } ) // 这里换成你的批量删除接口 await deleteBatchApi(selectedRows.value.map((item) item.id)) ElMessage.success(删除成功) tableRef.value.clearSelection() loadData() } const handleExport () { // 这里换成你的导出接口 exportApi(selectedRows.value.map((item) item.id)) } onMounted(() { loadData() }) /script核心代码就这么点。需要特别注意的是handleBatchDelete里接口调用成功之后才执行clearSelection()。如果你把清空操作放到接口前面一旦接口失败用户选中的数据已经没了再次点击删除时发现选不中目标交互体验会非常差。3.3 Vue 2 Element UI 完整实现存量项目里 Vue 2 Element UI 还是主力写法逻辑一样只是实例方法和 this 的用法不同。template div classtable-page div classtoolbar el-button typedanger :disabled!selectedRows.length clickhandleBatchDelete 批量删除{{ selectedRows.length }} /el-button el-button :disabled!selectedRows.length clickhandleExport 批量导出 /el-button /div el-table refmultipleTable :datatableData row-keyid selection-changehandleSelectionChange el-table-column typeselection width55 / el-table-column propname label姓名 / el-table-column propphone label手机号 / el-table-column propcreateTime label创建时间 / /el-table /div /template script export default { data() { return { tableData: [], selectedRows: [] } }, methods: { loadData() { // 这里换成你的列表接口 fetchUserList().then((res) { this.tableData res.data }) }, handleSelectionChange(rows) { this.selectedRows rows }, handleBatchDelete() { this.$confirm(确定删除选中的 ${this.selectedRows.length} 条数据吗, 提示, { type: warning }).then(async () { // 这里换成你的批量删除接口 await deleteBatchApi(this.selectedRows.map((item) item.id)) this.$message.success(删除成功) this.$refs.multipleTable.clearSelection() this.loadData() }) }, handleExport() { // 这里换成你的导出接口 exportApi(this.selectedRows.map((item) item.id)) } }, mounted() { this.loadData() } } /script两套代码的结构几乎是镜像的。核心就是selection-change存数据、按钮里消费数据、成功后clearSelection清状态。你只要把这条链路刻在脑子里换什么版本都能很快写出来。3.4 两个容易忽略的关键点第一个关键点是接口成功后必须clearSelection。否则删除完数据表格刷新后那些选中的行还在选中状态里一旦用户不小心再点一次删除可能提交一批“已经不存在”的数据。虽然后端一般会做幂等或过滤但这是明显可以避免的脏状态。第二个关键点是selection-change回调里不要动tableData。因为表格的数据来源是tableData你在回调里修改它会触发表格重新渲染进而可能再次触发选中状态变化。运气好只是多触发几次回调运气差就直接页面卡死了。回调里只存数据其他事情交给按钮事件这是安全边界。我见过不少项目把“选中行数统计”放在 selection-change 里用 computed 去做这个思路没问题但千万别在回调里对 tableData 本身做变更。你要做的只是保存选中结果UI 上的统计数量完全可以用表达式selectedRows.length直接渲染。4. 进阶场景跨页选中、默认选中和全选联动4.1 分页后选中状态为什么没了这是后台系统里非常常见的需求用户在第一页勾了几行翻到第二页再勾几行最后在第三页点批量提交发现第一页勾选的数据丢了。默认情况下el-table 跨页选中就是会丢的原因还得回到第一章节说的“选中状态由组件内部维护”。分页切换时tableData变了组件内部维护的选中集合还存着旧的行对象引用而新页面渲染出来的行是新的数据引用对不上号于是跨页就“失忆”了。可以打个比方你把趴在桌上的三只猫记在了一个小本本上过一会儿主人把桌上的猫全换了小本本上还是原来三只猫的名字。新来的猫不在本子上自然也不会被勾中。虽然小本本还在但对应的猫已经不在这张桌子上了。4.2 正确姿势reserve-selection 配 row-key官方给的方案是reserve-selection。正确写法是把它加在 selection 列上同时给 el-table 配置row-keyel-table refmultipleTable :datatableData row-keyid selection-changehandleSelectionChange el-table-column typeselection width55 reserve-selection / /el-table注意reserve-selection是加在el-table-column上的不是加在el-table上的。我见过很多同学把它写到 el-table 的属性里结果翻页后还是丢失选中状态排查半天找不到原因。这个细节官方文档写得很清楚但架不住实际操作时手滑。同时表格必须配置row-key否则reserve-selection不生效。row-key的值必须是每行数据的唯一标识通常是后端返回的 id不要用 index。index 在翻页后会重复组件根本没法判断哪一行是同一个数据源。配置完成后选中状态会按 row 的唯一 key 记录下来翻页也不会丢。4.3 默认选中和回显nextTick 里操作的坑编辑页回显是一个典型场景进入页面时之前保存过的选中行要重新勾上。很多人的第一反应是直接遍历数据调用toggleRowSelection但发现没反应原因在于表格还没渲染完。正确方式是用nextTick等表格渲染完成后再逐行操作this.$nextTick(() { this.tableData.forEach((row) { if (preSelectedIds.includes(row.id)) { this.$refs.multipleTable.toggleRowSelection(row, true) } }) })这里的row必须来自tableData数组里的同一个对象引用。如果你用接口返回的另一个数据副本去调用toggleRowSelection组件会认为这个行不在表格内操作无效。回显操作会触发selection-change事件进而更新你维护的选中数组这本身是正常的。但如果你在 selection-change 里又做了其他操作比如再次调用 toggleRowSelection就可能出现递归触发。我在项目里遇到过这种循环现象是勾一个复选框页面上闪烁好几次。排查到最后发现就是回显逻辑和 selection-change 互相套娃了解决办法是加一个标记位或者在回显前解除监听、回显完再恢复。4.4 手动控制全选选中集合与 UI 的互通默认全选由表头的复选框完成但业务上有时需要更灵活的“全选”比如“全选今天新增的数据”“全选某些状态的行”。这时候直接操作表头复选框就做不到了得自己来控制。一个可行方案是拿到当前页数据后逐个调用toggleRowSelection(row, true)const handleSelectAllByCondition () { const rowsToSelect this.tableData.filter( (row) row.status active ) rowsToSelect.forEach((row) { this.$refs.multipleTable.toggleRowSelection(row, true) }) }取消全选也同理把第二个参数改成 false 就行。这种做法的好处是它把控制权交给你而不是让组件去帮你判断。当你需要做“部分过滤后全选”时手动方案几乎是唯一的选择。如果你不想影响表头复选框的自动全选状态而是想手动控制表头那个复选框的显示状态那就要看你的表格数据是否全都满足条件了。最简单的实现是用计算属性判断当前页tableData中满足条件的行数量再用reserve-selectiontoggleRowSelection去驱动状态。表头复选框的勾选与否本质上由当前选中集合是否等于当前页所有可选项来决定el-table 内部会自己算你不需要额外处理。5. 常见问题与排查技巧实录5.1 表格 reload 后选中状态错乱现象刷新列表数据后旧选中状态残留或者选中的行对不上号。原因没有配置唯一的row-key或者row-key用了会变化的值。比如有人用 index 当 row-key插入一条数据后所有行的索引都变了组件内部维护的选中集合就乱了。还有人用后端返回的对象作为 row-key对象引用一变状态全部失效。解决给 el-table 配上稳定的row-key比如 id。如果使用了reserve-selection更必须保证 row-key 唯一稳定。另外不要在接接口后对表格数据重新赋值时把原对象拆散重组尽量保留原始行的引用结构。5.2 selection-change 被触发的次数不合理现象只勾选了一行回调却执行了好几次甚至页面卡死。原因回调里修改了tableData或者修改了行对象的某个字段触发表格重新渲染再次引发选中状态变化形成循环。解决回调里只做数据存储不做数据更新。如果必须基于选中数据更新页面用 computed 或单独的处理函数不要在回调里直接改 data。还有一个隐蔽的坑如果你用了响应式状态去存选中行数组在回调里又对数组里的元素做追加、删除操作也会触发响应式更新从而影响表格渲染。稳妥的做法是直接整体赋值selectedRows.value rows不要对数组再做二次修改。5.3 多选列不显示或全选失效现象selection 列没出来或者全选复选框点击没反应。原因最常见的是 selection 列被v-if包裹在动态列切换时组件状态没有正常创建另一个原因是typeselection拼写错误或者 el-table-column 上不小心写了多个 type 属性。还有一个场景是用户自作聪明给 selection 列也写了prop这会影响组件内部对多选列的处理逻辑。解决检查 selection 列的 type 是否拼写正确、是否唯一动态列时给 el-table-column 加key强制重建列组件。如果确认写法没问题但还是不显示可以试着把 selection 列放在表格列的最前面有些版本对列顺序有隐藏的依赖。5.4 el-table 多选高频问题速查表问题现象可能原因解决办法跨页后选中行消失没有配置 row-key 和 reserve-selection给 el-table 加稳定 row-key给 selection 列加 reserve-selectiontoggleRowSelection 调用后没反应表格数据未渲染或传入的行对象不是 tableData 中的同一引用用 nextTick 等渲染完成确保 row 来自 tableDataselection-change 反复触发回调里修改了 tableData 或行对象字段回调中只保存选中数据不做数据更新批量操作后选中状态残留没有调用 clearSelection接口成功后调用 this.$refs.multipleTable.clearSelection()动态切换数据源后选中错乱没有唯一 row-key 或 row-key 不稳定配置稳定的 row-key如 id全选复选框不生效selection 列配置错误或动态列未重建检查 typeselection 写法动态列加 key回显时勾选状态闪动回显逻辑与 selection-change 互相触发用标记位控制或回显前临时解除监听这个表是我排查问题时最常用的参考。每一条都是在实际项目中踩过的坑不是从文档里抄出来的。遇到问题先对表自查大多数情况一两分钟就能定位。最后分享一个我这两年带项目时养成的习惯凡是表格批量操作我都会在列表页统一封装一个getSelectedRows()方法内部根据是否配置了reserve-selection决定是直接返回维护的选中行数组还是从 refs 里读取。这样团队成员写业务的时候只需要调用一个方法不用每次重复思考“我这里到底该用 selection-change 还是 refs”。另外如果你们项目后续要搞多个表格导出一个 Excel 这种高级导出功能多选里维护好的选中行数据就是第一批要传入的数据源提前把数据结构梳理干净后面做导出会省很多事。
返回列表