Redux Toolkit(TypeScript)中数组筛选排序搜索的正确选择器实现
你的Reselect选择器实现问题及修正方案
你当前的实现存在记忆化失效的问题:每次调用selectBy(filterBy, sorting, search)都会创建一个全新的createSelector实例,导致Reselect无法复用之前的计算缓存——组件每次渲染都会触发重新计算,和直接在组件里写逻辑没区别。
另外还有一个潜在问题:原代码中items.sort()会直接修改Redux Store里的原数组,违反了Redux状态不可变的原则,可能引发意外的状态变更。
正确实现分两种场景:
场景1:筛选/排序/搜索参数存在Redux Store中
如果filterBy、sorting、search已经存在Redux切片里,直接把它们作为选择器的输入依赖,创建单一的记忆化选择器:
// 在slice文件中 const selectItems = (state: RootState) => state.product.items; const selectFilterBy = (state: RootState) => state.product.filterBy; const selectSorting = (state: RootState) => state.product.sorting; const selectSearch = (state: RootState) => state.product.search; export const selectFilteredSortedItems = createSelector( [selectItems, selectFilterBy, selectSorting, selectSearch], (items, filterBy, sorting, search) => { // 先复制原数组,避免修改Redux中的原始状态 const lowerSearch = search.toLowerCase(); return [...items] // 先筛选分类(减少后续排序的数组长度,提升性能) .filter(item => filterBy !== "all" ? item.category === filterBy : true) // 排序 .sort((a, b) => b[sorting] - a[sorting]) // 搜索筛选 .filter(item => item.title.toLowerCase().includes(lowerSearch) || item.category.toLowerCase().includes(lowerSearch) || item.ingredients.some(ing => ing.toLowerCase().includes(lowerSearch)) ); } );
组件中直接使用:
const items = useAppSelector(selectFilteredSortedItems);
这种方式下,选择器是固定实例,Reselect会自动监听四个输入值的变化,只有当其中任一值改变时才重新计算,完全发挥记忆化的作用。
场景2:筛选/排序/搜索参数是组件本地状态
如果这些参数是组件的useState本地状态,需要用useMemo缓存selectBy返回的选择器实例,避免每次渲染都创建新的选择器:
// 组件中 const [filterBy, setFilterBy] = useState<CategoriesType>("all"); const [sorting, setSorting] = useState<SortType>("price"); const [search, setSearch] = useState(""); // 缓存选择器实例,只有当参数变化时才重新创建 const memoizedSelector = useMemo(() => { return selectBy(filterBy, sorting, search); }, [filterBy, sorting, search]); const items = useAppSelector(memoizedSelector);
同时要修改slice中的selectBy,确保排序时不修改原数组:
// slice文件中修正后的selectBy export const selectBy = ( filterBy: CategoriesType, sorting: SortType, search: string ) => createSelector(selectItems, (items) => { const lowerSearch = search.toLowerCase(); return [...items] .filter((item) => filterBy !== "all" ? item.category === filterBy : true ) .sort((a, b) => b[sorting] - a[sorting]) .filter( (item) => item.title.toLowerCase().includes(lowerSearch) || item.category.toLowerCase().includes(lowerSearch) || item.ingredients.some((ing) => ing.toLowerCase().includes(lowerSearch)) ); });
额外优化建议
- 把
search的小写转换提前做一次,避免在filter循环中重复执行,提升性能 - 调整操作顺序:先做分类筛选(减少数组长度),再排序,最后做搜索筛选,比先排序再筛选更高效(因为排序的时间复杂度是O(n log n),数组越短排序越快)
内容的提问来源于stack exchange,提问作者GTB
相关产品推荐
相关产品推荐

