React大型应用中文档列表筛选性能优化方案咨询
React大型文档列表筛选的性能优化问题
我正在开发一个大型React应用,其中某页面展示文档列表。目前用状态变量实现筛选功能,但担心现有方案存在卡顿、列表更新延迟的问题。
现有状态定义
从数据库获取的文档列表存储在状态中:
const [ documents, setDocuments ] = useState<Array<Document>>([]);
筛选条件对应的状态:
const [ createdBy, setCreatedBy ] = useState<string>('Any'); const [ search, setSearch ] = useState<string>(''); const [ open, setOpen ] = useState<Array<string>>([]); const [ onlyCert, setOnlyCert ] = useState<number>(0); const [ modifiedFilter, setModifiedFilter ] = useState<number>(0); const [ farFromAverageFilter, setFarFromAverageFilter ] = useState<boolean>(false); const [ draftFilter, setDraftFilter ] = useState<boolean>(false); const [ dupeFilter, setDupeFilter ] = useState<boolean>(false); const [ sourceFilter, setSourceFilter ] = useState<string>('Any'); const [ ageFilter, setAgeFilter ] = useState<[number, number]>([0,0]); const [ filteredDocs, setFilteredDocs ] = useState([]);
筛选逻辑实现
通过useEffect钩子将筛选结果存入filteredDocs:
useEffect(() => { setFilteredDocs((documents ?.filter(e => ( search === '' ? e : `${e.data.vehicle.v_stock_no}${e.data.vehicle.v_make}${e.data.vehicle.v_model}${e.data.vehicle.v_vin_no}${e.data.vehicle.company_name}`.toLowerCase().includes(search.toLowerCase().replace(/\s/g , ''))) && (createdBy === 'Any' ? e : e.metadata.created_by_user_id === createdBy) && (onlyCert % 3 === 0 ? e : onlyCert % 3 === 1 ? e.data.vehicle.v_is_certified : !e.data.vehicle.v_is_certified) && (modifiedFilter % 3 === 0 ? e : modifiedFilter % 3 === 1 ? e.metadata.created_at === e.metadata.updated_at : e.metadata.created_at !== e.metadata.updated_at) && (farFromAverageFilter ? e.risky.length > 0 : e) && (sourceFilter === 'Any' ? e : (e?.data?.vehicle?.v_source?.toUpperCase() || '') === sourceFilter.toUpperCase()) && (dupeFilter ? (documents.filter(x => e.data.vehicle.v_stock_no === x.data.vehicle.v_stock_no).length > 1 && documents.filter(x => e.data.vehicle.v_stock_no === x.data.vehicle.v_stock_no).every(e => !e.rollback)) || (documents.filter(x => e.data.vehicle.v_vin_no === x.data.vehicle.v_vin_no).length > 1 && documents.filter(x => e.data.vehicle.v_vin_no === x.data.vehicle.v_vin_no).every(e => !e.rollback)) : e) && (draftFilter ? e.draft : e) && (Math.abs(e.data.vehicle.v_days) >= ageFilter[0] && Math.abs(e.data.vehicle.v_days) <= ageFilter[1]) ))) }, [ documents, search, createdBy, onlyCert, modifiedFilter, farFromAverageFilter, sourceFilter, dupeFilter, draftFilter, ageFilter ])
当前困境
最初直接在渲染列表的map方法里应用筛选,导致每次渲染都重复执行筛选逻辑,后来改成用useEffect缓存结果,但还是希望找到更高效的优化方案,要求无需刷新页面、不用URL查询参数。
性能优化方案
1. 用useMemo替代useEffect缓存筛选结果
useMemo会在依赖变化时重新计算并直接返回结果,避免额外的状态更新(setFilteredDocs会触发组件重渲染),减少不必要的渲染次数:
const filteredDocs = useMemo(() => { if (!documents) return []; // 提前处理搜索关键词,避免每次filter重复计算 const processedSearch = search.toLowerCase().replace(/\s/g, ''); return documents.filter(e => { // 提前拼接搜索目标字符串并转小写 const searchTarget = `${e.data.vehicle.v_stock_no}${e.data.vehicle.v_make}${e.data.vehicle.v_model}${e.data.vehicle.v_vin_no}${e.data.vehicle.company_name}`.toLowerCase(); const matchesSearch = processedSearch === '' || searchTarget.includes(processedSearch); if (!matchesSearch) return false; // 创建者筛选 const matchesCreatedBy = createdBy === 'Any' || e.metadata.created_by_user_id === createdBy; if (!matchesCreatedBy) return false; // 认证状态筛选 let matchesCert = true; if (onlyCert % 3 !== 0) { matchesCert = onlyCert % 3 === 1 ? e.data.vehicle.v_is_certified : !e.data.vehicle.v_is_certified; } if (!matchesCert) return false; // 修改状态筛选 let matchesModified = true; if (modifiedFilter % 3 !== 0) { matchesModified = modifiedFilter % 3 === 1 ? e.metadata.created_at === e.metadata.updated_at : e.metadata.created_at !== e.metadata.updated_at; } if (!matchesModified) return false; // 风险项筛选 if (farFromAverageFilter && e.risky.length === 0) return false; // 来源筛选 const matchesSource = sourceFilter === 'Any' || (e?.data?.vehicle?.v_source?.toUpperCase() || '') === sourceFilter.toUpperCase(); if (!matchesSource) return false; // 草稿筛选 if (draftFilter && !e.draft) return false; // 天数筛选 const days = Math.abs(e.data.vehicle.v_days); return days >= ageFilter[0] && days <= ageFilter[1]; }); }, [documents, search, createdBy, onlyCert, modifiedFilter, farFromAverageFilter, sourceFilter, draftFilter, ageFilter]);
2. 预计算重复项集合
把重复项的计算单独抽出来用useMemo缓存,避免每次筛选都遍历整个文档列表:
const duplicateStockVins = useMemo(() => { const stockNoCounts = new Map(); const vinNoCounts = new Map(); const nonRollbackDocs = documents.filter(doc => !doc.rollback); nonRollbackDocs.forEach(doc => { const stockNo = doc.data.vehicle.v_stock_no; stockNoCounts.set(stockNo, (stockNoCounts.get(stockNo) || 0) + 1); const vinNo = doc.data.vehicle.v_vin_no; vinNoCounts.set(vinNo, (vinNoCounts.get(vinNo) || 0) + 1); }); return { duplicateStockNos: new Set([...stockNoCounts.entries()].filter(([_, count]) => count > 1).map(([stockNo]) => stockNo)), duplicateVinNos: new Set([...vinNoCounts.entries()].filter(([_, count]) => count > 1).map(([vinNo]) => vinNo)) }; }, [documents]);
然后在筛选逻辑里直接用集合判断:
// 重复项筛选 if (dupeFilter) { const matchesDupe = duplicateStockVins.duplicateStockNos.has(e.data.vehicle.v_stock_no) || duplicateStockVins.duplicateVinNos.has(e.data.vehicle.v_vin_no); if (!matchesDupe) return false; }
3. 提前处理固定筛选逻辑
对onlyCert、modifiedFilter这类数值型筛选条件,提前计算出对应的判断规则,减少筛选时的分支重复计算:
const [certFilterRule] = useMemo(() => { switch(onlyCert % 3) { case 1: return (doc) => doc.data.vehicle.v_is_certified; case 2: return (doc) => !doc.data.vehicle.v_is_certified; default: return () => true; } }, [onlyCert]);
筛选时直接调用certFilterRule(e)即可。
4. 列表虚拟化
如果文档数量极大(上万条),用react-window或react-virtualized实现列表虚拟化,只渲染当前视口内的文档项,大幅减少DOM节点数量,提升渲染性能。
5. 后端分页+筛选
若前端处理数据量过大,建议后端支持分页和筛选逻辑,前端只请求当前页的筛选结果,减少前端需要处理的数据量。
内容的提问来源于stack exchange,提问作者MaxInteg
相关产品推荐
相关产品推荐

