Node.js分段传输API数据至React实现渐进式显示方案问询
实现Express后端分段传输数据,让React端流式加载内容
要解决这个问题,我们需要把原来的「一次性返回全部数据」改成分段流式传输,让后端拿到部分数据就立刻发给前端,React端收到一段就更新一段内容。下面分两步修改你的代码:
第一步:修改Express后端代码
原来的代码是等Axios完全拿到所有数据才调用res.send,现在我们提供两种方案,你可以根据数据量大小选择:
方案1:分批发送已获取的完整数据(适合中小数据量)
如果API返回的是JSON数组,我们可以先拿到完整数组,然后每隔几百毫秒发送一部分数据给前端,实现简单,不需要额外依赖:
const express = require('express'); const axios = require('axios'); const cors = require('cors'); const app = express(); app.use(cors()); app.use(express.json()); app.get('/getdata', async function (req, res) { try { // 先获取完整数据数组 const response = await axios(`${APIurl}?${APIKey}`); const dataArray = response.data; // 设置响应头为分块传输,告知浏览器要持续接收数据 res.setHeader('Transfer-Encoding', 'chunked'); res.setHeader('Content-Type', 'application/json'); // 先发送数组开头标记 res.write('['); // 分批发送每个元素,每隔300毫秒发一个(可调整间隔时间) let isFirstItem = true; for (const item of dataArray) { if (!isFirstItem) { res.write(','); // 数组元素之间添加逗号分隔 } isFirstItem = false; res.write(JSON.stringify(item)); // 等待一段时间再发下一个,模拟分段加载的效果 await new Promise(resolve => setTimeout(resolve, 300)); } // 发送数组结尾标记,结束响应 res.write(']'); res.end(); } catch (error) { console.error('获取数据失败:', error); res.status(500).send('获取数据失败'); } }); app.listen(5000);
方案2:流式接收API数据并实时转发(适合超大数据量)
如果API返回的是超大数组,或者本身支持流式输出,我们可以让Axios以流式方式接收数据,实时解析并转发给前端,不需要等待全部数据加载完成,能大幅降低后端内存占用:
const express = require('express'); const axios = require('axios'); const cors = require('cors'); const { createParser } = require('stream-json'); // 需要安装stream-json库 const { streamArray } = require('stream-json/streamers/StreamArray'); const { pipeline } = require('stream/promises'); const app = express(); app.use(cors()); app.use(express.json()); app.get('/getdata', async function (req, res) { try { // 设置响应头为分块传输 res.setHeader('Transfer-Encoding', 'chunked'); res.setHeader('Content-Type', 'application/json'); // 发送数组开头标记 res.write('['); let isFirstItem = true; // 用Axios流式接收API数据 const response = await axios({ url: `${APIurl}?${APIKey}`, responseType: 'stream' }); // 初始化JSON流式解析器,提取数组中的每个元素 const parser = createParser(); const streamer = streamArray(); // 监听解析出来的每个数组元素,实时转发给前端 streamer.on('data', ({ value }) => { if (!isFirstItem) { res.write(','); } isFirstItem = false; res.write(JSON.stringify(value)); }); // 解析完成后,发送数组结尾标记并结束响应 streamer.on('end', () => { res.write(']'); res.end(); }); // 把API的数据流导入解析器,开始处理 await pipeline(response.data, parser, streamer); } catch (error) { console.error('流式传输失败:', error); res.status(500).send('流式传输失败'); } }); app.listen(5000);
注意:方案2需要安装
stream-json库用于流式解析JSON数组,执行npm install stream-json即可完成安装。
第二步:修改React前端代码
原来的Axiosget方法会等待全部响应完成才触发then,现在我们需要用Fetch API的可读流来逐块读取响应内容,解析出完整数据后立刻更新state:
import React, { useState, useEffect } from 'react'; const Projects = () => { const [data, updateData] = useState([]); useEffect(() => { const fetchDataStream = async () => { try { const response = await fetch('http://localhost:5000/getdata'); const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let partialData = ''; let arrayStarted = false; while (true) { const { done, value } = await reader.read(); if (done) break; // 把二进制数据解码为文本 const chunk = decoder.decode(value, { stream: true }); partialData += chunk; // 处理数组开头的标记 if (!arrayStarted && partialData.startsWith('[')) { partialData = partialData.slice(1); arrayStarted = true; } // 分割出完整的对象(按逗号分隔) const items = partialData.split(','); // 最后一个元素可能不完整,留到下一次循环处理 partialData = items.pop() || ''; // 把完整的对象转成JSON并更新state const newItems = items .filter(item => item.trim() !== '') .map(item => { // 处理数组结尾的标记 if (item.endsWith(']')) { return JSON.parse(item.slice(0, -1)); } return JSON.parse(item); }); if (newItems.length > 0) { updateData(prev => [...prev, ...newItems]); } } // 处理最后剩下的部分数据 if (partialData.trim() !== '' && partialData.endsWith(']')) { const lastItem = JSON.parse(partialData.slice(0, -1)); updateData(prev => [...prev, lastItem]); } } catch (error) { console.log("流式加载错误:", error); } }; fetchDataStream(); }, []); return ( <div> {data.map(project => ( <div key={project["id"]}> <div>{project["name"]}</div> </div> ))} </div> ); }; export default Projects;
核心原理说明
- 后端通过
Transfer-Encoding: chunked响应头告知浏览器,这是一个分块传输的响应,会持续发送数据直到调用res.end()。 - React端利用
Fetch API的ReadableStream接口,逐块读取响应内容,解析出完整的数据对象后立刻更新state,实现「收到一段就显示一段」的流式加载效果。
内容的提问来源于stack exchange,提问作者Shivang Garg
相关产品推荐
相关产品推荐

