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
相关产品推荐
相关产品推荐

