Redux Toolkit实现商品筛选排序如何精简冗余useEffect
商品筛选页冗余useEffect优化方案
原实现核心问题
当前代码的核心问题是把派生数据当成独立状态维护:筛选排序后的商品列表完全是基于原始商品数据、Redux筛选参数计算出来的派生值,不需要用useState存储,更不需要多个useEffect串行更新。
原有写法的明显缺陷:
- 多个
useEffect按顺序触发状态更新,会导致组件多次重渲染,性能损耗大 - 每个effect的依赖数组需要重复添加大量依赖,很容易出现漏写、错写,导致筛选逻辑不触发或意外触发
- 串行执行筛选/排序逻辑容易出现覆盖问题,比如清空某一个筛选条件时,之前effect执行的结果会残留,导致筛选结果错误
- 原有多选匹配逻辑有bug:直接展开size/brand数组做判断,无法支持多个尺寸、多个品牌同时选中的筛选场景
最优实现:用useMemo计算派生值,完全移除冗余useEffect
所有筛选、排序逻辑可以统一收敛到useMemo中,不需要维护单独的filteredProducts状态,也不需要任何相关的useEffect。
第一步:获取源数据
直接从Redux和useQuery拿到最原始的数据源,不做中间状态存储:
const { size, brand, price, sort } = useSelector((state) => state.filter); const { loading, error, data } = useQuery(GET_PRODUCTS); // 给原始商品数据加空数组兜底,避免接口返回空时报错 const products = data?.getProducts || [];
第二步:统一计算筛选+排序结果
遵循先筛选、后排序的顺序(减少排序需要处理的数据量,性能更好),在useMemo中完成所有计算:
const filteredProducts = useMemo(() => { // 第一步:遍历原始商品,匹配所有筛选条件 let result = products.filter((product) => { // 尺寸匹配:未选尺寸默认返回全部商品,选中尺寸则匹配任意一个选中值 const sizeMatched = size.length === 0 ? true : size.some((selectedSize) => product.size.includes(Number(selectedSize))); // 品牌匹配:逻辑同尺寸 const brandMatched = brand.length === 0 ? true : brand.some((selectedBrand) => product.brand.includes(selectedBrand)); // 价格区间匹配:未选区间默认返回全部,选中则判断价格在区间内(包含边界值) const priceMatched = price.length === 0 ? true : product.price >= price[0][0] && product.price <= price[0][1]; return sizeMatched && brandMatched && priceMatched; }); // 第二步:对筛选后的结果做排序 if (sort.length > 0) { // 注意先拷贝数组再排序,Array.sort是原地修改,会污染原始商品数据 result = result.slice().sort((a, b) => { return sort.includes('price-highest') ? b.price - a.price : a.price - b.price; }); } return result; // 依赖只需要包含所有用到的源数据即可,逻辑清晰不会漏写 }, [products, size, brand, price, sort]);
Redux中存储的筛选状态结构参考:
方案优势
- 代码量缩减60%以上,没有任何冗余的状态更新逻辑,所有计算规则集中在一处,可读性极强
- 不会触发多次重渲染,所有计算在单次渲染周期内完成,性能更好
- 不存在依赖数组配置错误问题:只要原始商品数据、任意筛选参数变化,会自动重新计算最终结果,清空筛选条件时也会自动回到全量商品列表,不会残留旧筛选结果
- 修复了原逻辑中多选筛选不生效、价格边界值被过滤、sort原地修改原数组的隐蔽bug
扩展优化(复杂筛选场景适用)
如果后续要增加更多筛选维度,可以把每个筛选规则抽成独立纯函数,进一步降低维护成本:
// 抽离独立筛选规则 const filterChecks = { matchSize: (product, selectedSizes) => selectedSizes.length === 0 || selectedSizes.some(s => product.size.includes(Number(s))), matchBrand: (product, selectedBrands) => selectedBrands.length === 0 || selectedBrands.some(b => product.brand.includes(b)), matchPrice: (product, priceRange) => priceRange.length === 0 || (product.price >= priceRange[0][0] && product.price <= priceRange[0][1]) } const filteredProducts = useMemo(() => { let result = products.filter(product => filterChecks.matchSize(product, size) && filterChecks.matchBrand(product, brand) && filterChecks.matchPrice(product, price) ); if (sort.length > 0) { result = result.slice().sort((a, b) => sort.includes('price-highest') ? b.price - a.price : a.price - b.price); } return result; }, [products, size, brand, price, sort]);
内容的提问来源于stack exchange,提问作者עמית שוקרון
相关产品推荐
相关产品推荐

