You need to enable JavaScript to run this app. React JS下用扩展运算符合并数组实现AsyncSelect无限滚动求解
优惠活动
大模型
产品
解决方案
定价
更多

React JS下用扩展运算符合并数组实现AsyncSelect无限滚动求解

核心问题排查
  • 选项返回结构错误:原代码return [...fetchedData, allData]生成的是二维嵌套数组,已加载的allData是整个被当成单个数组元素塞进返回列表的,根本不是扁平的选项集合,React Select没法正确解析这类结构,会渲染出异常的选项内容。
  • 页码更新时机完全错误:原代码里的useEffect同时依赖scrolll和page两个值,组件首次挂载就会触发setPage(page + 1),不等用户操作就会持续把页码加到很高,发起大量错误页的无效请求。
  • 滚动触发逻辑断层:原代码的scroll方法里手动调用promiseOptions(),根本没有走React Select loadOptions的标准返回流程,组件识别不到手动调用的结果,也不会把返回的数据追加到已渲染的选项列表里。
  • 状态闭包陷阱:filterData方法里拿到的page、allData都是组件上一次渲染的旧值,拼接数据的时候很容易出现重复、漏加的问题,拉取的页码和实际需要的页码对不上。
  • 初始化阶段的拼接逻辑缺失:原代码只有page > 1的时候才会把拉到的数据往allData里存,第一页加载完成的选项根本没被存入缓存,第一次滚动的时候直接拿空的allData做拼接,列表内容完全错乱。
适配后的实现方案

调整思路:

  • 把页码、加载锁这类需要跨渲染周期保持最新值的变量存在useRef里,避免闭包拿到旧状态
  • 把“页码递增”的动作移到“成功拉取下一页数据之后”执行,完全去掉挂载时自动递增页码的副作用
  • 所有返回给React Select的选项都做扁平化处理,保证数组结构是一维的{label, value}对象集合
  • 加简单的加载锁,避免滚动触底时连续触发重复请求

对应实现代码:

import React, { useState, useRef } from "react";
import AsyncSelect from "react-select/async";

const WithPromises = () => {
  // 存储所有已加载的扁平选项
  const [allOptions, setAllOptions] = useState([]);
  // ref 存储当前待加载的页码,避免闭包旧值
  const currentPageRef = useRef(1);
  // ref 存储加载状态锁,防止重复请求
  const isLoadingRef = useRef(false);

  // 通用分页数据拉取方法
  const requestPage = async (targetPage) => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos?_limit=15&_page=${targetPage}`
    );
    const rawList = await response.json();
    // 接口返回空时代表没有更多数据,直接返回空数组
    if (!rawList.length) return [];
    // 转换为组件要求的选项格式
    return rawList.map(item => ({
      label: item.title,
      value: item.title
    }));
  };

  // loadOptions 标准回调
  const handleLoadOptions = async () => {
    // 首次打开下拉时,加载第一页数据
    if (allOptions.length === 0) {
      const firstPageData = await requestPage(currentPageRef.current);
      setAllOptions(firstPageData);
      currentPageRef.current += 1;
      return firstPageData;
    }
    // 后续分页加载走滚动回调逻辑,直接返回已缓存的所有选项
    return allOptions;
  };

  // 滚动到底部的回调
  const handleMenuScrollToBottom = async () => {
    // 正在加载时直接返回,拦截重复请求
    if (isLoadingRef.current) return;
    isLoadingRef.current = true;
    try {
      const nextPageData = await requestPage(currentPageRef.current);
      // 没有更多数据就直接结束加载
      if (!nextPageData.length) return;
      // 把新拉取的选项拼到已有列表末尾,更新状态
      setAllOptions(prevOptions => [...prevOptions, ...nextPageData]);
      currentPageRef.current += 1;
    } finally {
      // 无论请求成功失败,都释放加载锁
      isLoadingRef.current = false;
    }
  };

  return (
    <AsyncSelect
      cacheOptions
      onMenuScrollToBottom={handleMenuScrollToBottom}
      isClearable
      isSearchable
      defaultOptions
      loadOptions={handleLoadOptions}
    />
  );
};

export default WithPromises;

修复效果说明:

  • 首次打开下拉会正常加载第一页15条选项
  • 每次滚动条触底时,会自动拉取下一页15条数据,拼到现有选项列表的末尾,已加载的历史选项不会丢失
  • 加载锁机制会拦截连续滚动触发的重复请求,不会出现同一个页码被多次拉取的问题
  • 所有返回的选项都是一维扁平结构,组件可以正常解析渲染

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 04:36:03