优化React中4000+对象数组过滤时的性能问题
性能优化方案
针对你处理4000+对象数组时的卡顿问题,核心瓶颈在于大量DOM节点渲染、重复计算和不合理的状态更新,以下是具体优化步骤:
一、用虚拟列表减少DOM渲染(最关键优化)
直接渲染4000+ DOM元素会导致浏览器重绘重排过载,虚拟列表只渲染可视区域内的元素,大幅降低DOM节点数量。
简单虚拟列表实现示例
import { useState, useRef, useEffect } from 'react'; // 抽离虚拟列表组件 const VirtualAirportList = ({ airports }: { airports: Airport[] }) => { const [scrollTop, setScrollTop] = useState(0); const listRef = useRef<HTMLDivElement>(null); const ITEM_HEIGHT = 32; // 匹配你的列表项实际高度 useEffect(() => { const handleScroll = () => { listRef.current && setScrollTop(listRef.current.scrollTop); }; const currentRef = listRef.current; currentRef?.addEventListener('scroll', handleScroll); return () => currentRef?.removeEventListener('scroll', handleScroll); }, []); // 计算可视区域的起始/结束索引 const startIndex = Math.floor(scrollTop / ITEM_HEIGHT); const endIndex = Math.min(startIndex + 15, airports.length); // 可视区域固定显示15条,可调整 const visibleItems = airports.slice(startIndex, endIndex); return ( <div ref={listRef} className="airports-list mt-8 search-results-airports h-28 overflow-y-scroll scroll" style={{ position: 'relative' }} > {/* 占位元素,保证滚动条高度正确 */} <div style={{ height: `${airports.length * ITEM_HEIGHT}px`, pointerEvents: 'none' }} /> {/* 可视区域元素容器 */} <div style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${startIndex * ITEM_HEIGHT}px)` }} > {visibleItems.map((airport) => ( <div key={airport.id} className="flex justify-between" style={{ height: `${ITEM_HEIGHT}px` }}> <p className="font-bold text-sm">{airport.country}</p> <p className="text-xs">{airport.name}</p> </div> ))} </div> </div> ); };
二、优化搜索逻辑与状态管理
- 预处理缓存搜索字段:提前将所有机场的搜索字段转为小写,避免每次过滤时重复调用
toLowerCase() - 分离搜索状态与列表状态:不再直接修改数组状态,而是通过搜索关键词动态计算过滤结果,减少大数组状态更新
修改后的核心逻辑
const { airports } = useContext(MainContext); // 预处理数据,缓存小写后的字段(仅初始化时执行一次) const [processedAirports] = useState(() => airports.map(airport => ({ ...airport, nameLower: airport.name.toLowerCase(), countryLower: airport.country.toLowerCase(), cityLower: airport.city.toLowerCase() })) ); // 仅保存搜索关键词,而非整个过滤后的数组 const [searchKeyword, setSearchKeyword] = useState(''); // 动态计算过滤结果,无需存入状态 const filteredAirports = searchKeyword ? processedAirports.filter(airport => airport.nameLower.includes(searchKeyword) || airport.countryLower.includes(searchKeyword) || airport.cityLower.includes(searchKeyword) ) : processedAirports;
简化输入框事件
删除原来的searchAirports和checkIfEmpty函数,直接绑定关键词更新:
<input type="text" name="From" className="w-full border rounded-xl h-11 p-4 text-sm focus:border-blue-500 outline-none focus:border-2" placeholder="Start your flight search" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value.toLowerCase())} />
三、移除冗余的状态同步代码
原代码中初始化airportListLocal的useEffect是冗余的,因为useState已经直接用airports初始化,直接删除这段代码:
// 删掉以下冗余代码 useEffect(() => { setAirportListLocal(airports); }, []);
四、用React.memo优化列表项
将列表项抽成独立组件并包裹React.memo,避免无关更新导致的重渲染:
const AirportItem = React.memo(({ airport }: { airport: Airport }) => ( <div className="flex justify-between"> <p className="font-bold text-sm">{airport.country}</p> <p className="text-xs">{airport.name}</p> </div> )); // 渲染时使用该组件 {visibleItems.map((airport) => ( <AirportItem key={airport.id} airport={airport} /> ))}
五、防抖优化实时搜索(可选)
如果实时输入仍有轻微卡顿,给关键词更新添加防抖,减少过滤计算频率:
const debounce = (fn: Function, delay: number) => { let timeoutId: NodeJS.Timeout; return (...args: any[]) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn(...args), delay); }; }; const debouncedSetKeyword = useCallback(debounce((value: string) => { setSearchKeyword(value.toLowerCase()); }, 100), []); // 输入框 onChange 替换为 onChange={(e) => debouncedSetKeyword(e.target.value)}
内容的提问来源于stack exchange,提问作者Amos Machora
相关产品推荐
相关产品推荐

