You need to enable JavaScript to run this app. 基于HackerNews API实现批量请求边取数边分页渲染的问题
优惠活动
大模型
产品
解决方案
定价
更多

基于HackerNews API实现批量请求边取数边分页渲染的问题

实现分批流式渲染的核心方案

当前逻辑无法实时渲染的根因是:所有ID的详情请求被统一打包到单个Promise.all中,必须等所有请求完成才会触发一次状态更新,自然无法做到边拉取边渲染。

核心调整逻辑

  • 将全量500个ID按固定批次(可按需调整大小,匹配预期的每批10个)切分
  • 逐批发起限流后的详情请求,每批的Promise全部完成后,立刻将该批次结果追加到状态数组触发重渲染,不等待后续批次
  • 兼容原有前端分页逻辑:随着结果数组持续追加,分页组件的总页数会自动同步,无需改动现有分页计算规则
  • 补充类型切换时的状态重置逻辑,避免新旧分类的数据混杂
  • 修复原有代码中setLoading(false)被同步执行、未等待请求完成就关闭加载态的bug

具体代码修改

1. request.js 核心改造

移除冗余的Promise包装,增加批次切分、逐批更新逻辑:

import axios from 'axios';
import BottleNeck from 'bottleneck'

const limiter = new BottleNeck({
  maxConcurrent: 1, 
  minTime: 333
})

// 按需求配置单批拉取的ID数量
const BATCH_SIZE = 10;

export const Request = (setResults, searchBarType, setLoading, shouldCancel) => {
  const searchBar = type => {
    const obj = {
      'new': 'newstories',
      'past': '',
      'comments': 'user',
      'ask': 'askstories',
      'show': 'showstories',
      'jobs': 'jobstories',
      'top': 'topstories',
      'best': 'beststories',
      'user': 'user'
    }
    return obj[type] ? obj[type] : obj['new'];
  }

  let type = searchBar(searchBarType)

  const getData = () => {
    const options = type
    const API_URL = `https://hacker-news.firebaseio.com/v0/${options}.json?print=pretty`;
    // 移除冗余的new Promise包装,直接返回axios实例
    return axios.get(API_URL);
  }

  const getIdFromData = (dataId) => {
    const API_URL = `https://hacker-news.firebaseio.com/v0/item/${dataId}.json?print=pretty`;
    return limiter.schedule(() => axios.get(API_URL));
  }

  const runAsyncFunctions = async () => {
    setLoading(true)
    // 新请求触发时先清空历史结果,避免旧数据残留
    setResults([])
    const {data: allIds} = await getData()

    // 全量ID切分为固定大小的批次
    const idBatches = []
    for (let i = 0; i < allIds.length; i += BATCH_SIZE) {
      idBatches.push(allIds.slice(i, i + BATCH_SIZE))
    }

    // 逐批执行请求,每批完成立刻更新状态
    for (const batch of idBatches) {
      // 检查是否触发取消逻辑(比如用户切换了搜索类型)
      if (shouldCancel?.()) return
      
      const batchResults = await Promise.all(
        batch.map(id => getIdFromData(id).then(res => res.data))
      )
      // 每批拉完立刻追加到结果数组,触发组件重渲染
      setResults(prev => [...prev, ...batchResults])
    }

    setLoading(false)
  }  

  runAsyncFunctions()
}

2. App.js 适配调整

原有分页计算逻辑无需改动,仅需补充请求取消的竞态处理,避免旧请求干扰新状态:

import React, { useState, useEffect } from 'react';
import './App.css';
import { SearchBar } from './search-bar';
import { Results } from './results';
import { Request } from '../helper/request'
import { Pagination } from './pagination';

function App() {
  const [results, setResults] = useState([]);
  const [searchBarType, setsearchBarType] = useState('news');
  const [loading, setLoading] = useState(false);
  const [currentPage, setCurrentPage] = useState(1);
  const [resultsPerPage] = useState(3);
  

  const handleClick = (e) => {
    const serachBarButtonId = e.target.id;
    setsearchBarType(serachBarButtonId)
    // 切换类型时重置到第一页
    setCurrentPage(1)
  }
  
  useEffect(() => {
    // 增加取消标识,解决旧请求未完成时切换分类的竞态问题
    let isCancelled = false
    Request(
      setResults, 
      searchBarType, 
      setLoading,
      () => isCancelled
    )
    // 依赖更新时标记当前请求为取消
    return () => {
      isCancelled = true
    }
  }, [searchBarType])

  // 原有分页逻辑完全保留,随结果数组追加自动更新总页数
  const indexOfLastResult = currentPage * resultsPerPage;
  const indexOfFirstResult = indexOfLastResult - resultsPerPage;
  const currentResults = results.slice(indexOfFirstResult, indexOfLastResult);

  const paginate = (pageNumber) => setCurrentPage(pageNumber); 


  return (
    <div className="App">
      <SearchBar handleClick={handleClick} />
      <Results results={currentResults} loading={loading} />
      <Pagination resultsPerPage={resultsPerPage} totalResults={results.length} paginate={paginate} />
    </div>
  );
}

export default App;

实现后效果

  • 第一批次10个ID的详情请求完成后,前端立刻渲染这10条数据,同时生成分页对应的页码,无需等待全量500条数据拉完
  • 后续每批10个ID拉取完成,就自动追加到结果数组,分页总页数自动从少到多逐步更新,用户可以边加载边浏览已拿到的数据
  • 原有限流逻辑完全保留,不会触发接口频率限制
  • 解决了分类切换时的状态混杂、旧请求干扰新页面的竞态问题

小提示:如果需要调整单批拉取的粒度,只需修改BATCH_SIZE的数值即可,无需改动其他逻辑。如果觉得每批10个的加载速度还可以再优化,也可以适当调大maxConcurrent的并发数,在接口允许的范围内提升拉取效率。

内容的提问来源于stack exchange,提问作者Seb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 05:27:04