Vue

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才解决了这个问题。