
本文介绍在 redux toolkit 中过滤数据的两种推荐方式:使用派生状态(selector)避免直接修改原始数据,或在必要时将过滤结果单独存入 state,确保数据源完整性与可预测性。
本文介绍在 redux toolkit 中过滤数据的两种推荐方式:使用派生状态(selector)避免直接修改原始数据,或在必要时将过滤结果单独存入 state,确保数据源完整性与可预测性。
在 Redux 应用中,直接修改 state.notices 数组来实现过滤是一种反模式。这会破坏原始数据的完整性,导致无法恢复全量列表、影响缓存一致性、干扰时间旅行调试,并可能引发 UI 同步问题(例如多个组件依赖不同过滤视图时相互覆盖)。正确的做法是将“过滤”视为一种派生计算,而非状态变更。
✅ 推荐方案一:使用 selector 计算过滤结果(推荐)
这是最符合 Redux 原则的方式——保持 notices 为唯一真实数据源,将搜索条件(searchOption 和 searchValue)作为独立状态字段,再通过 selector 动态计算出当前匹配的通知列表。
// noticesSlice.ts
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
notices: [] as Notice[], // 假设 Notice 类型已定义
searchOption: "Simple" as "Simple" | "Advanced",
searchValue: "",
};
export const noticesSlice = createSlice({
name: "notices",
initialState,
reducers: {
setSearch: (state, action) => {
const { searchOption, search } = action.payload;
state.searchOption = searchOption;
state.searchValue = search?.toLowerCase() ?? "";
},
resetSearch: (state) => {
state.searchOption = "Simple";
state.searchValue = "";
},
},
extraReducers: (builder) => {
builder.addCase(getNotices.fulfilled, (state, { payload }) => {
state.notices = payload;
});
},
});
export const { setSearch, resetSearch } = noticesSlice.actions;
export default noticesSlice.reducer;接着定义一个 memoized selector(推荐配合 createSelector 提升性能):
// selectors.ts
import { createSelector } from "@reduxjs/toolkit";
import type { RootState } from "./store"; // 根据你的 store 路径调整
export const selectNoticesState = (state: RootState) => state.notices;
export const selectFilteredNotices = createSelector(
selectNoticesState,
(noticesState) => {
const { notices, searchOption, searchValue } = noticesState;
if (!searchValue.trim()) return notices;
return notices.filter((note) => {
const value = searchValue;
switch (searchOption) {
case "Simple":
return note.title.toLowerCase().includes(value);
case "Advanced":
return (
note.description.toLowerCase().includes(value) ||
note.tags.some((tag) => tag.toLowerCase().includes(value))
);
default:
return true;
}
});
}
);在组件中使用:
import { useSelector, useDispatch } from "react-redux";
import { setSearch } from "./noticesSlice";
import { selectFilteredNotices } from "./selectors";
function NoticesList() {
const dispatch = useDispatch();
const filteredNotices = useSelector(selectFilteredNotices);
const handleSearch = (searchOption: string, search: string) => {
dispatch(setSearch({ searchOption, search }));
};
return (
<div>
<SearchBar onSearch={handleSearch} />
<ul>
{filteredNotices.map((note) => (
<li key={note.id}>{note.title}</li>
))}
</ul>
</div>
);
}✅ 优势:
- 原始 notices 永远不变,便于复位、分页、导出等操作;
- selector 自动 memoize,仅当依赖项变化时重新计算;
- 符合 Redux “state 是只读的”核心原则。
⚠️ 方案二:显式维护 filteredNotices 字段(仅限特殊需求)
仅当你需要持久化过滤结果(如服务端分页+前端二次过滤、离线缓存过滤态等),才考虑在 state 中新增字段:
const initialState = {
notices: [] as Notice[],
filteredNotices: [] as Notice[], // 显式存储过滤结果
};
// 在 reducers 中:
filterNotices: (state, action) => {
const { searchOption, search } = action.payload;
const value = search?.toLowerCase() ?? "";
if (!value.trim()) {
state.filteredNotices = [];
return;
}
if (searchOption === "Simple") {
state.filteredNotices = state.notices.filter(
(n) => n.title.toLowerCase().includes(value)
);
} else if (searchOption === "Advanced") {
state.filteredNotices = state.notices.filter(
(n) =>
n.description.toLowerCase().includes(value) ||
n.tags.some((t) => t.toLowerCase().includes(value))
);
}
},⚠️ 注意事项:
- 必须确保 filteredNotices 不参与任何异步请求的响应更新(即 extraReducers 中只更新 notices);
- 需额外提供 clearFilter 或 resetFilter action 来还原为空数组;
- 若未严格隔离,极易造成 notices 与 filteredNotices 数据不一致。
? 总结
- ❌ 避免在 reducer 中直接 state.notices = state.notices.filter(...) —— 这是状态污染;
- ✅ 优先使用 selector + 独立搜索状态字段,轻量、可预测、易测试;
- ? 如需缓存过滤结果,请明确建模为 filteredNotices 字段,并保证其更新逻辑单一、可追溯;
- ? 补充建议:对 searchValue 执行 .trim().toLowerCase() 并做空值防御,提升鲁棒性。
遵循这一模式,你的通知过滤功能将更健壮、可维护,并与 Redux Toolkit 的设计理念深度契合。

















