layui.table渲染万级数据闪烁的本质是DOM批量重绘与响应式劫持叠加所致;必须关闭height未设值、page为false、loading:true三项默认行为,并在done回调中用DocumentFragment批量插入tbody,后端必须分页返回数据。
本质是 dom 批量重绘 + vue 式响应式劫持的叠加效应。layui 2.8+ 虽改用原生 js,但 table.render() 默认仍把全部数据塞进 tbody,浏览器要一次性生成上万个 tr 节点,触发强制同步布局(layout)和重绘(paint),肉眼就看到“白屏→闪一下→出来”。更麻烦的是,如果启用了 even、skin 或自定义 templet,每个单元格还要走一遍字符串拼接或函数调用,cpu 时间直接拉满。
不关它们,加再多优化都是白搭:
height 设为具体数值(如 height: 500),否则表格无法启用虚拟滚动,scrollY 机制压根不生效page 必须设为 true,且 limit 控制在 50–100;万级数据不分页,table.reload() 时仍会全量重绘loading: true 的默认动画——它底层用 setTimeout 插入 loading dom,和真实渲染竞争 DOM 树,反而加剧抖动done 回调接管渲染时机别依赖 table.render() 自动完成,万级数据下它的内部 render 流程不可控。把实际插入逻辑挪到 done 里,等 layui 完成表头、分页器等轻量结构后再动手:
table.render({ elem: '#demo', height: 500, url: '/api/list', page: true, limit: 80, done: function(res, curr, count) { // 此时表头、分页已就位,只操作 tbody const tbody = this.elem.next('.layui-table-box').find('tbody'); tbody.empty(); // 清空可能残留的 loading 占位 // 这里可手写字符串拼接,或用 DocumentFragment 批量 append const frag = document.createDocumentFragment(); res.data.forEach(row => { const tr = document.createElement('tr'); tr.innerHTML = `<td>${row.id}</td><td>${row.name}</td>`; frag.appendChild(tr); }); tbody[0].appendChild(frag); }});
这是最容易被忽略的「假优化」:有人试图用 data 传入全部万条数据,再靠 page: true 前端切片——layui 内部仍会遍历整个数组做字段映射、格式化、筛选,内存占用翻倍,GC 频繁导致卡顿更明显。
page 和 limit 参数,只返回当前页数据templet 里调用异步函数(比如查用户头像),这会让每一行都触发微任务,渲染队列崩坏filter
真正卡住的从来不是 layui,是没切断数据流的源头。后端少吐一条无效字段,比前端加十个 debounce 都管用。