API返回超千条数据后创建动态输入框卡顿,求优化方案
优化数千个输入框渲染卡顿的方案
核心问题
一次性渲染数千个DOM输入框会阻塞主线程,导致页面卡顿——这不是循环逻辑的性能问题,而是同步批量渲染大量DOM节点引发的主线程阻塞。
具体优化方法
1. 虚拟滚动(最推荐)
只渲染当前可视区域内的输入框,非可视区域的元素不渲染,彻底减少DOM节点数量。
以react-window为例:
首先安装依赖:
npm install react-window
修改代码:
import { FixedSizeList as List } from 'react-window'; import React, { useEffect, useState, memo } from 'react'; // 封装纯组件输入框,避免不必要重渲染 const InputCell = memo(({ data, header, accessorKey }) => ( <input name={`${data}_${header}`} style={{ width: '50px' }} /> )); // 表格行组件 const TableRow = memo(({ index, style, columns, tableData }) => { const rowData = tableData[index]; return ( <div style={style} className="table-row"> <span>{rowData.dept}</span> {columns.map(col => ( <InputCell key={col.id} data={rowData.dept} header={col.header} accessorKey={col.accessorKey} /> ))} </div> ); }); function YourComponent() { const [columns, setColumns] = useState([]); const [tableData, setTableData] = useState([]); useEffect(() => { const tableWorker = new Worker(URL.createObjectURL(new Blob([`(${editInputData.toString()})()`]))); tableWorker.postMessage({ planData }); tableWorker.addEventListener('message', (e) => { const { param1, param2 } = e.data; const newColumns = Array.isArray(param1) ? param1.map((curr, index) => ({ header: curr, accessorKey: index + 1, id: index + 1 })) : []; setColumns(newColumns); const newTableData = Array.isArray(param2) ? param2.map(data => ({ dept: data, ...newColumns.reduce((acc, curr) => { acc[curr.accessorKey] = true; // 仅存标记,渲染时再创建输入框 return acc; }, {}) })) : []; setTableData(newTableData); }); }, []); return ( <List height={500} // 可视区域高度 itemCount={tableData.length} itemSize={30} // 每行高度 width="100%" > {({ index, style }) => ( <TableRow index={index} style={style} columns={columns} tableData={tableData} /> )} </List> ); }
2. 分批渲染(无依赖快速实现)
将数据分成多个批次,利用requestIdleCallback或setTimeout分批更新状态,让浏览器在空闲时间渲染一批元素,避免主线程阻塞。
修改消息监听部分的代码:
tableWorker.addEventListener('message', (e) => { const { param1, param2 } = e.data; const newColumns = Array.isArray(param1) ? param1.map((curr, index) => ({ header: curr, accessorKey: index + 1, id: index + 1 })) : []; setColumns(newColumns); // 拆分数据为批次,每批20条可根据实际调整 const batchSize = 20; const batches = []; for (let i = 0; i < param2.length; i += batchSize) { batches.push(param2.slice(i, i + batchSize)); } // 分批更新状态 let currentBatch = 0; const renderBatch = () => { if (currentBatch >= batches.length) return; const batchData = batches[currentBatch].map(data => ({ dept: data, ...newColumns.reduce((acc, curr) => { acc[curr.accessorKey] = <input name={`${data}_${curr.header}`} style={{ width: '50px' }} />; return acc; }, {}) })); // 合并已有数据和当前批次 setTableData(prev => [...prev, ...batchData]); currentBatch++; // 让浏览器空闲时处理下一批 requestIdleCallback(renderBatch); }; // 初始化清空数据并开始分批渲染 setTableData([]); requestIdleCallback(renderBatch); });
3. 优化循环逻辑
原代码中reduce使用扩展运算符每次创建新对象,会产生大量临时对象,可改为直接赋值优化:
// 原列生成逻辑 acc = [...acc, { header: curr, accessorKey: index + 1, id: index + 1 }] // 优化后 acc.push({ header: curr, accessorKey: index + 1, id: index + 1 }); return acc; // 原输入框对象生成逻辑 acc = { ...acc, [curr?.accessorKey]: <input ... /> } // 优化后 acc[curr.accessorKey] = <input ... />; return acc;
总结
优先使用虚拟滚动方案,从根源减少DOM节点数量;如果不能引入第三方库,用分批渲染快速缓解卡顿;同时优化循环中的对象创建逻辑,减少内存开销。
内容的提问来源于stack exchange,提问作者user22378454
相关产品推荐
相关产品推荐

