You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 22:52:56