Warning: [antdv: Each record in table should have a unique key prop,or set rowKey to an unique primary key.]错误处理
一 项目架构说明
新手直接上手项目,前端采用Vue框架+后端springboot架构。硬着头皮边学边用,天天都是瓶颈。
二 错误现象
在前端页面上打开开发者模式上,看到有报错:
Warning: [antdv: Each record in table should have a unique `key` prop,or set `rowKey` to an unique primary key.] warning @ warning.js?2149:7 warning.js?2149:7 Warning: [antdv: Table] Each record in dataSource of table should have a unique `key` prop, or set `rowKey` of Table to an unique primary key, warning @ warning.js?2149:7
三 错误分析和处理
请教前端老鸟同事,说是前端页面上,组件上少了key和rowKey的属性设置。
1出错的前端页面代码:
<a-table :columns="columns" :dataSource="data" :loading="loading" :pagination="pagination" bordered size="small" @change="handleTableChange" > <template slot="action" slot-scope="text, record"> <a-button icon="edit" type="primary" class="btn_margin" @click="showModal(record, 'edit')" >编辑</a-button> <a-button icon="copy" type="primary" class="btn_margin" @click="showModal(record, 'copy')" >复制</a-button> <a-button icon="delete" type="danger" class="btn_margin" @click="deleteConfirm(record)" >删除</a-button> </template> </a-table> ... 其它代码 ... let columnsCN = [ { title: "配置类型编码", dataIndex: "typeCode", align: "center" }, { title: "配置类型名称", dataIndex: "typeName", align: "center" }, { title: "配置名称", dataIndex: "name", align: "center" }, { title: "配置值", dataIndex: "value", align: "center", ellipsis: true, }, { title: "备注", dataIndex: "remark", align: "center" }, { title: "操作", key: "action", scopedSlots: { customRender: "action" }, align: "center", width: 300 } ];
2 修改后的代码:
<a-table :columns="columns" :dataSource="data" :loading="loading" :pagination="pagination" rowKey="id" bordered size="small" @change="handleTableChange" > <template slot="action" slot-scope="text, record"> <a-button icon="edit" type="primary" class="btn_margin" @click="showModal(record, 'edit')" >编辑</a-button> <a-button icon="copy" type="primary" class="btn_margin" @click="showModal(record, 'copy')" >复制</a-button> <a-button icon="delete" type="danger" class="btn_margin" @click="deleteConfirm(record)" >删除</a-button> </template> </a-table> ... 其它代码 ... let columnsCN = [ { title: "配置类型编码", dataIndex: "typeCode", align: "center", key: "typeCode" }, { title: "配置类型名称", dataIndex: "typeName", align: "center", key: "typeName" }, { title: "配置名称", dataIndex: "name", align: "center", key: "name" }, { title: "配置值", dataIndex: "value", align: "center", ellipsis: true, key: "value" }, { title: "备注", dataIndex: "remark", align: "center", key:"remark" }, { title: "操作", dataIndex: "action", key: "action", scopedSlots: { customRender: "action" }, align: "center", width: 300, } ];
修改保存之后,直接在Chrome上,打开控制台,不再有上述错误出现。
四 为什么要这么修改就不报错呢
指出问题错误的过程中,同事让我打开AntDesignVue的官网,然后搜索table组件,链接到
https://antdv.com/components/table/#Table
尝试找到页面上的一个例子,点开代码,然后,看到官方的规范写法:
然后,在官网的该页面的最下方:
Note #
The values inside dataSource and columns should follow this in Table, and dataSource[i].key would be treated as key value default for dataSource. If dataSource[i].key is not provided, then you should specify the primary key of dataSource value via rowKey. If not, warnings will show in browser console. // primary key is uid return <Table rowKey="uid" />; // or return <Table rowKey={record => record.uid} />;
在项目的Chrome控制台界面,进入network选项卡,找到访问路径,点击preview,看到后端给的数据是
也就是说,前端页面上的table组件里面的数据来源于 :dataSource=”data”
data又是这个数组:
该数组最终由后端接口提供:
最后,进入后端程序中查看Controller:
@RestController(value = "配置管理") @RequestMapping(value = "/config") @Api(tags = "config", value = "配置管理") public class SamConfigController { @Autowired private ISamConfigManageService samConfigManageService; @RequestMapping(value = "/list", name = "配置列表") @ApiOperation(value = "配置列表", notes = "") @VerifyToken public SamResponseVO list(@RequestBody PageVO<SamConfigVO> condition) { SamResponseVO samResponseVO = new SamResponseVO(); samResponseVO.setRespSuccess(true); Page page = samConfigManageService.queryConfigPageList(condition); samResponseVO.setData(page); return samResponseVO; }
返回值是一个SamResponseVO类型,再进去看,是把Page对象封装到SamResponseVO的data字段里了。进而查看Page对象的定义:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.onlyou.framework.mybatis.dao.pojo; import java.io.Serializable; import java.util.Collections; import java.util.List; public class Page implements Serializable { private static final long serialVersionUID = -4313766497412557907L; private int pageSize = 10; private int totalRecord = 0; private int currentPage = 1; private int totalPage; private List records = Collections.emptyList(); public Page() { } public Page(int currentPage, int pageSize) { this.currentPage = currentPage; this.pageSize = pageSize; } public Page(int currentPage, int totalRecord, int pageSize) { this.currentPage = currentPage; this.totalRecord = totalRecord; this.pageSize = pageSize; } public int getRecordStart() { return this.currentPage > 0 ? (this.currentPage - 1) * this.pageSize + 1 : 0; } public int getRecordStartPrev() { return this.currentPage > 0 ? (this.currentPage - 1) * this.pageSize : 0; } public int getRecordEnd() { return this.currentPage > 0 ? this.currentPage * this.pageSize : 0; } public int getPageSize() { return this.pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public int getTotalRecord() { return this.totalRecord; } public void setTotalRecord(int totalRecord) { this.totalRecord = totalRecord; } public int getCurrentPage() { return this.currentPage; } public void setCurrentPage(int currentPage) { this.currentPage = currentPage; } public int getTotalPage() { this.totalPage = (int)Math.floor((double)this.totalRecord * 1.0D / (double)this.pageSize); if (this.totalRecord % this.pageSize != 0) { ++this.totalPage; } return this.totalPage == 0 ? 1 : this.totalPage; } public <T> List<T> getRecords() { return this.records; } public <T> void setRecords(List<T> records) { this.records = records; } public static class FieldDomain { public static final String RECORD_START = "recordStart"; public static final String RECORD_START_PREV = "recordStartPrev"; public static final String RECORD_END = "recordEnd"; public static final String PAGE_SIZE = "pageSize"; public static final String TOTAL_RECORD = "totalRecord"; public static final String CURRENT_PAGE = "currentPage"; public static final String TOTAL_PAGE = "totalPage"; public static final String RECORDS = "records"; public FieldDomain() { } } }
最终,我们知道前端页面的data的数据来源了。
{respSuccess: true, respMsg: null, respCode: null, rcvDate: null, rcvTime: null,…} data: {pageSize: 10, totalRecord: 74, currentPage: 1, totalPage: 8,…} currentPage: 1 pageSize: 10 recordEnd: 10 recordStart: 1 recordStartPrev: 0 records: [{rowState: 0, id: "727C0EZOVYGKSLPJBXFG7AHATMP2N3IE", typeCode: "VERIFY_DELIVERY_STATUS",…},…] 0: {rowState: 0, id: "727C0EZOVYGKSLPJBXFG7AHATMP2N3IE", typeCode: "VERIFY_DELIVERY_STATUS",…} 1: {rowState: 0, id: "I97CYG4CLHK55PRY8MQCLK97E430ZYJC", typeCode: "DEVICE_BILL_NUM", typeName: "票箱统计数量",…} 2: {rowState: 0, id: "42QPPVPIR6HRK9HW4UTSLUNSHX5H3U5W", typeCode: "DEVICE_REMIB_NUM",…} 3: {rowState: 0, id: "UXURZ9E4D9IV6BQYTSR940SD53JIZMY4", typeCode: "DEVICE_BILL_NUM", typeName: "票箱累计数量",…} 4: {rowState: 0, id: "51CYIKWS3B7OT0J89PDM7O003HMS3IRS", typeCode: "DEVICE_SEND_EMAIL",…} 5: {rowState: 0, id: "AMMOYZRMA4ZVQPJVAEDKAPBRI5VV33EW", typeCode: "DEVICE_SEND_EMAIL",…} 6: {rowState: 0, id: "3750879f0a4241308818e17eaa921751", typeCode: "MONITOR_ERROR_CODE",…} 7: {rowState: 0, id: "044775c953a6442f8d1c323c3f259435", typeCode: "MONITOR_ERROR_CODE",…} 8: {rowState: 0, id: "b7206ea918d84012a3773abfc8d9ce5d", typeCode: "MONITOR_ERROR_CODE",…} 9: {rowState: 0, id: "b03d734f2e1c4a158664b187e035bcc2", typeCode: "MONITOR_ERROR_CODE",…} totalPage: 8 totalRecord: 74 rcvDate: null rcvTime: null respCode: null respMsg: null respSuccess: true
所以,我们把前端页面上table组件上加上rowKey=”id才解决了这个问题。
一条评论
Pingback: