如何在单次请求中向客户端传输1万条数据无卡顿?可采用流式分块传输吗
解决方案
你要的边接收边使用数据的流式效果,可通过以下两种方案实现,优先推荐第一种,改造成本最低、性能最优:
方案1:HTTP分块流式传输(NDJSON格式)
核心是服务端开启Transfer-Encoding: chunked分块编码,按行传输单条JSON数据(NDJSON格式),客户端逐行解析增量更新数据,无需等待全量响应返回。同时配合数据库流式查询,避免服务端一次性加载全量数据到内存。
服务端代码改造
/** * 服务端 dashboard 接口 */ import { Request, Response } from 'express'; public dashboard = async (req: Request, res: Response) => { // 设置流式响应头 res.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8'); res.setHeader('Transfer-Encoding', 'chunked'); res.status(200); try { // knex开启流式查询,不会一次性加载全量数据到服务端内存 const dataStream = knex('dashboard').select().stream(); for await (const row of dataStream) { // 单条数据转JSON后加换行符分隔,写入响应流 res.write(`${JSON.stringify(row)}\n`); } // 所有数据传输完成,结束响应 res.end(); } catch (err) { res.write(`${JSON.stringify({ error: err.message })}\n`); res.end(); } };
客户端MobX存储改造
/** * 客户端 MobX 存储 */ import axios from 'axios'; import { makeAutoObservable, runInAction } from 'mobx'; class Store { data = []; constructor() { makeAutoObservable(this); } public getData = async () => { try { const response = await axios({ url: 'http://localhost:3000/dashboard', method: 'GET', // 开启流式响应支持,axios >= 1.4.0 版本可用fetch适配器 responseType: 'stream', adapter: 'fetch' }); const decoder = new TextDecoder('utf-8'); const reader = response.data.getReader(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; // 解码二进制流并拼接到缓冲区 buffer += decoder.decode(value, { stream: true }); // 按换行符拆分完整数据行 const lines = buffer.split('\n'); // 保留不完整的行到下一次解析 buffer = lines.pop() || ''; // 逐行解析数据,增量更新到存储 for (const line of lines) { if (!line.trim()) continue; try { const row = JSON.parse(line); if (row.error) throw new Error(row.error); runInAction(() => { this.data.push(row); }); } catch (parseErr) { console.error('数据行解析失败:', parseErr); } } } } catch (err) { console.error(err.message || err); } }; }
如果axios版本低于1.4.0不支持fetch适配器,可直接替换为原生fetch实现流式请求,解析逻辑完全一致。
前端组件优化
现有React组件无需修改即可自动响应数据增量更新,建议给Table开启虚拟滚动,避免数据量过大导致DOM渲染卡顿:
<Table rowKey="id" className={`home__table`} columns={columns} dataSource={data} // 开启虚拟滚动,只渲染可视区域的行 scroll={{ y: 600 }} virtual />
方案2:分页拉取(备选)
如果不想改造流式逻辑,可实现无限滚动分页:服务端新增page、pageSize参数支持分页查询,前端首次拉取前100条数据直接渲染,滚动到列表底部再自动拉取下一页。该方案兼容性更强、逻辑更简单,唯一不足是需要前端额外处理分页状态。
注意事项
- 流式传输时不要开启gzip压缩,部分代理服务器会缓存gzip内容导致流式失效,需要压缩可使用brotli流式压缩
- 1万条数据量级下开启Table虚拟滚动可大幅降低渲染性能消耗,避免页面卡顿
内容的提问来源于stack exchange,提问作者CODEHUB
相关产品推荐
相关产品推荐

