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

ReactJS中Select组件加载大量数据卡顿的优化咨询

React Select加载大量数据卡顿的优化方案

当原生<select>渲染上千个<option>时,过多的DOM节点会直接导致组件挂载和交互卡顿,以下是几个高效的优化方案:


1. 虚拟滚动(Virtual Scrolling)

只渲染当前视口内的选项,避免一次性生成所有DOM节点,这是解决大量列表渲染卡顿最直接的方案。可以使用react-window轻量库实现:

首先安装依赖:

npm install react-window

修改代码:

import React, { useState, useEffect } from "react";
import { FixedSizeList as List } from "react-window";

// 自定义虚拟滚动的Option组件
const VirtualOption = ({ index, style, data }) => {
  const option = data[index];
  return (
    <option
      style={style}
      key={option.id_titulos}
      value={option.id_titulos}
    >
      {option.carrera}
    </option>
  );
};

export default function App() {
  const [carrera, setCarrera] = useState([]);
  const [selectedValue, setSelectedValue] = useState("");

  useEffect(() => {
    const getCarreras = async () => {
      const req = await fetch(
        "https://witalenco.com.co/apiWittytalent/dyc/titulos/carreras.php"
      );
      const res = await req.json();
      setCarrera(res);
    };
    getCarreras();
  }, []);

  return (
    <div>
      <select
        name="carrera"
        className="form-select"
        value={selectedValue}
        onChange={(e) => setSelectedValue(e.target.value)}
        style={{ height: "40px", overflow: "auto" }}
      >
        <option value="">--Seleccione --</option>
        <List
          height={300}
          itemCount={carrera.length}
          itemSize={30}
          itemData={carrera}
        >
          {VirtualOption}
        </List>
      </select>
    </div>
  );
}

2. 服务端分页与搜索过滤

如果数据量极大(上万条),可以和后端配合,实现分页加载或搜索时动态请求匹配数据,避免前端一次性获取所有数据:

示例(搜索触发请求):

import React, { useState, useEffect, useCallback } from "react";
import debounce from "lodash.debounce";

export default function App() {
  const [carrera, setCarrera] = useState([]);
  const [searchKeyword, setSearchKeyword] = useState("");

  // 防抖处理搜索请求
  const fetchFilteredCarreras = useCallback(
    debounce(async (keyword) => {
      if (!keyword.trim()) {
        // 搜索为空时加载默认数据(比如第一页)
        const req = await fetch(
          "https://witalenco.com.co/apiWittytalent/dyc/titulos/carreras.php?page=1"
        );
        const res = await req.json();
        setCarrera(res);
        return;
      }
      // 根据关键词请求过滤后的数据
      const req = await fetch(
        `https://witalenco.com.co/apiWittytalent/dyc/titulos/carreras.php?search=${encodeURIComponent(keyword)}`
      );
      const res = await req.json();
      setCarrera(res);
    }, 300),
    []
  );

  useEffect(() => {
    fetchFilteredCarreras(searchKeyword);
    return () => fetchFilteredCarreras.cancel();
  }, [searchKeyword, fetchFilteredCarreras]);

  return (
    <div>
      <input
        type="text"
        placeholder="搜索专业..."
        value={searchKeyword}
        onChange={(e) => setSearchKeyword(e.target.value)}
        className="form-control mb-2"
      />
      <select name="carrera" className="form-select">
        <option>--Seleccione --</option>
        {carrera.map((item) => (
          <option key={item.id_titulos} value={item.id_titulos}>
            {item.carrera}
          </option>
        ))}
      </select>
    </div>
  );
}

注:需要后端配合实现search和page参数的接口逻辑


3. 使用高性能Select组件

替换原生<select>为专门优化过的第三方组件,比如react-select,它内置了虚拟滚动、搜索过滤等功能,开箱即用:

安装依赖:

npm install react-select

修改代码:

import React, { useState, useEffect } from "react";
import Select from "react-select";

export default function App() {
  const [options, setOptions] = useState([]);

  useEffect(() => {
    const getCarreras = async () => {
      const req = await fetch(
        "https://witalenco.com.co/apiWittytalent/dyc/titulos/carreras.php"
      );
      const res = await req.json();
      // 转换为react-select需要的格式
      const formattedOptions = res.map(item => ({
        value: item.id_titulos,
        label: item.carrera
      }));
      setOptions(formattedOptions);
    };
    getCarreras();
  }, []);

  return (
    <div>
      <Select
        options={options}
        placeholder="--Seleccione --"
        isSearchable
        // 开启虚拟滚动(react-select v5+支持)
        menuPortalTarget={document.body}
        components={{ MenuList: props => <div style={{ maxHeight: '300px', overflow: 'auto' }}>{props.children}</div> }}
      />
    </div>
  );
}

4. 前端本地防抖过滤

如果无法修改后端接口,可以在前端实现防抖+本地过滤,只渲染匹配关键词的选项,减少DOM数量:

import React, { useState, useEffect, useMemo } from "react";
import debounce from "lodash.debounce";

export default function App() {
  const [allCarreras, setAllCarreras] = useState([]);
  const [searchKeyword, setSearchKeyword] = useState("");

  useEffect(() => {
    const getCarreras = async () => {
      const req = await fetch(
        "https://witalenco.com.co/apiWittytalent/dyc/titulos/carreras.php"
      );
      const res = await req.json();
      setAllCarreras(res);
    };
    getCarreras();
  }, []);

  // 根据关键词过滤数据(useMemo避免不必要的重计算)
  const filteredCarreras = useMemo(() => {
    if (!searchKeyword.trim()) return allCarreras;
    return allCarreras.filter(item =>
      item.carrera.toLowerCase().includes(searchKeyword.toLowerCase())
    );
  }, [allCarreras, searchKeyword]);

  // 防抖更新搜索关键词
  const debouncedSetSearch = debounce((value) => {
    setSearchKeyword(value);
  }, 300);

  return (
    <div>
      <input
        type="text"
        placeholder="搜索专业..."
        onChange={(e) => debouncedSetSearch(e.target.value)}
        className="form-control mb-2"
      />
      <select name="carrera" className="form-select">
        <option>--Seleccione --</option>
        {filteredCarreras.map((item) => (
          <option key={item.id_titulos} value={item.id_titulos}>
            {item.carrera}
          </option>
        ))}
      </select>
    </div>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 03:47:52