自定义better-table元素rows属性配置行数与实际渲染行数缺失一行问题排查
解决better-table自定义元素行数缺失一行的问题
我帮你找到了问题的根源:你的addMultiRow方法是通过tbody.childNodes来统计和获取行元素,但你在tbody里添加了一个scrollbar div,这个div会被算在childNodes里,导致行计数和索引匹配出错。当你设置rows=10时,代码实际上只生成了9行tr,因为第一个childNodes位置被div占用了,循环逻辑没有处理这个额外元素的干扰。
具体问题点拆解
- 移除多余行的逻辑:你用
body.childNodes.length -1来代表当前行数,这个计算依赖div始终是tbody的第一个子元素,而且错误地把div的存在当成了“一行”的抵消,逻辑本身就不准确。 - 添加/更新行的循环:你用
body.childNodes[i]来获取第i行,但因为div的存在,i=0对应的是div而不是tr,导致循环到最后时少生成了一行。
解决方案
核心思路是直接针对tr元素进行统计和操作,完全忽略tbody里的其他元素,这样不管你在tbody里放多少额外元素(比如那个scrollbar div),都不会影响行的计数和生成。
需要修改两个部分的代码:
1. 修复addMultiRow方法中的行处理逻辑
把依赖childNodes的计数改成直接查询tr元素:
addMultiRow(body, dataCell = ['null']) { // 获取当前所有tr元素 const currentRows = Array.from(body.querySelectorAll('tr')); // Remove unnecessary rows while (currentRows.length > dataCell.length) { const lastRow = currentRows.pop(); lastRow.remove(); } // Add or update rows for (let i = 0; i < dataCell.length; i++) { let row = currentRows[i]; if (row) { this.setCell(row, this.#colsLength, dataCell[i]); } else { // Add missing rows this.addRow(body, dataCell[i]); } } }
2. 修复rows属性的数组生成逻辑
原来的new Array(len).fill(new Array(len).fill('1'))会导致所有行共享同一个数组引用,是一个潜在bug,改成动态生成每个行的独立数组:
case 'rows': len = parseInt(newValue); if (!isNaN(len) && newValue != oldValue) { // 为每一行生成独立的数组,避免引用共享 const rows = Array.from({length: len}, () => new Array(this.#colsLength).fill('1')); this.addMultiRow(this.tbody, rows); } break;
修改后的完整JavaScript代码
CSS和HTML部分无需修改,以下是修复后的完整JS代码:
'use strict'; class betterTable extends HTMLElement { #colsLength; constructor() { super(); this.#colsLength = 0; /* TABLE */ this.table = document.createElement('table'); /* HEADER */ this.thead = document.createElement('thead'); this.table.appendChild(this.thead); this.theadRow = document.createElement('tr'); this.thead.appendChild(this.theadRow); /* BODY */ this.tbody = document.createElement('tbody'); this.table.appendChild(this.tbody); this.scrollbar = document.createElement('div'); this.tbody.appendChild(this.scrollbar); } connectedCallback() { this.render(); } render() { this.appendChild(this.table); } setColumn(colsName = ['null']) { this.setCell(this.theadRow, colsName.length, colsName); this.#colsLength = colsName.length; this.cols = colsName.length; } setCell(row, len = this.#colsLength, cellText = new Array(this.#colsLength)) { // Remove unnecessary cells while (row.childNodes.length > len) row.lastChild.remove(); // Add missing cells for (let i = 0; len > row.childNodes.length; i++) { const cell = row.parentNode ? row.parentNode.tagName === 'THEAD' ? document.createElement('th') : document.createElement('td') : document.createElement('td'); row.appendChild(cell); } for (let i = 0; i < row.childNodes.length; i++) { if (typeof cellText[i] === 'string') { if (row.childNodes[i].innerText != cellText[i]) { row.childNodes[i].innerText = cellText[i]; } } else if (cellText[i] instanceof Element) { row.childNodes[i].appendChild = cellText[i]; } else { if (row.childNodes[i].innerText != 'null') { row.childNodes[i].innerText = 'null'; } } } } addRow(body, dataCell) { const row = document.createElement('tr'); this.setCell(row, this.#colsLength, dataCell); body.appendChild(row); } addMultiRow(body, dataCell = ['null']) { // 获取当前所有tr元素 const currentRows = Array.from(body.querySelectorAll('tr')); // Remove unnecessary rows while (currentRows.length > dataCell.length) { const lastRow = currentRows.pop(); lastRow.remove(); } // Add or update rows for (let i = 0; i < dataCell.length; i++) { let row = currentRows[i]; if (row) { this.setCell(row, this.#colsLength, dataCell[i]); } else { // Add missing rows this.addRow(body, dataCell[i]); } } } /* ///////////// Attributes ///////////// */ static get observedAttributes() { return ['cols', 'rows']; } attributeChangedCallback(name, oldValue, newValue) { let len; switch (name) { case 'cols': len = parseInt(newValue); if (!isNaN(len) && this.#colsLength !== len && newValue != oldValue) { this.setColumn(new Array(len)); } break; case 'rows': len = parseInt(newValue); if (!isNaN(len) && newValue != oldValue) { // 为每一行生成独立的数组,避免引用共享 const rows = Array.from({length: len}, () => new Array(this.#colsLength).fill('1')); this.addMultiRow(this.tbody, rows); } break; default: break; } } /* // GETTER & SETTER // */ get length() { return this.#colsLength; } get cols() { return this.getAttribute('cols'); } get rows() { return this.getAttribute('rows'); } set cols(num) { this.setAttribute('cols', num); } set rows(num) { this.setAttribute('rows', num); } } customElements.define('better-table', betterTable);
现在你设置rows='10'时,就能正确生成10行tr了,同时tbody里的scrollbar div也能保留。
内容的提问来源于stack exchange,提问作者DDHost
相关产品推荐
相关产品推荐

