
本文介绍如何使用 redux toolkit query(rtk query)替代硬编码的过滤器数据,通过调用外部 api 动态获取分类、职位类型等搜索条件,并安全、响应式地同步到 redux 状态中。
本文介绍如何使用 redux toolkit query(rtk query)替代硬编码的过滤器数据,通过调用外部 api 动态获取分类、职位类型等搜索条件,并安全、响应式地同步到 redux 状态中。
在构建 React + Redux 的搜索功能时,将过滤器选项(如 category、jobTypeList、datePost 等)写死在 initialState 中虽便于快速开发,但缺乏灵活性与可维护性。真实场景中,这些选项往往由后端统一管理(例如支持多语言、动态增删、权限控制),需通过 API 按需加载。Redux Toolkit Query(RTK Query)正是为此类数据获取与缓存场景而生的官方推荐方案——它不仅简化异步逻辑,还能自动集成请求生命周期、缓存、错误处理,并无缝更新 Redux 状态。
✅ 正确做法:用 RTK Query 加载并注入过滤器数据
1. 定义 API Slice(推荐路径:src/store/api.js)
// src/store/api.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: 'https://your-api.com/v1/' }),
endpoints: (builder) => ({
// 获取分类列表(如 Residential / Commercial)
fetchCategories: builder.query({
query: () => 'categories',
// 可选:添加 transformResponse 统一格式
transformResponse: (response) =>
response?.data?.map((item, idx) => ({
id: item.id || idx + 1,
name: item.name || 'Unknown',
value: item.slug || item.value || String(item.id),
})) || [],
}),
// 同理可定义其他过滤器端点
fetchJobTypes: builder.query({
query: () => 'job-types',
transformResponse: (response) =>
response?.data?.map((item, idx) => ({
id: item.id || idx + 1,
name: item.title,
value: item.key,
isChecked: false,
})) || [],
}),
}),
});
export const { useFetchCategoriesQuery, useFetchJobTypesQuery } = api;? 提示:transformResponse 可确保 API 返回结构与前端组件预期一致(例如补全 isChecked: false),避免运行时错误。
2. 配置 Store 并注入 API Reducer
// src/store/store.js
import { configureStore } from '@reduxjs/toolkit';
import { api } from './api';
import jobReducer from './jobSlice'; // 你原有的 slice
export const store = configureStore({
reducer: {
job: jobReducer,
[api.reducerPath]: api.reducer, // 必须注册
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware), // 必须添加 middleware
});3. 在 Slice 中响应 API 成功响应(关键步骤)
修改你的 jobSlice.js,在 createSlice 中添加 extraReducers,监听 RTK Query 的 fulfilled action:
// src/store/jobSlice.js
import { createSlice } from '@reduxjs/toolkit';
import { api } from './api';
const initialState = {
latestJob: [],
category: [], // ← 初始化为空数组,由 API 填充
jobTypeList: [],
datePost: [],
experienceLavel: [],
tags: [],
};
export const jobSlice = createSlice({
name: 'job',
initialState,
reducers: {
// 保留你原有的同步 reducer(addLatestJob, jobTypeCheck 等)
addLatestJob: (state, { payload }) => {
const isExist = state.latestJob.includes(payload);
if (isExist) {
state.latestJob = state.latestJob.filter((item) => item !== payload);
} else {
state.latestJob.push(payload);
}
},
// ... 其他 reducers(略)
},
// ? 新增:响应 API 请求成功
extraReducers: (builder) => {
builder
.addMatcher(
api.endpoints.fetchCategories.matchFulfilled,
(state, { payload }) => {
state.category = payload; // ✅ 直接覆盖 category 字段
}
)
.addMatcher(
api.endpoints.fetchJobTypes.matchFulfilled,
(state, { payload }) => {
state.jobTypeList = payload; // ✅ 自动带 isChecked: false
}
);
// 可继续添加其他端点匹配逻辑
},
});
export const {
addLatestJob,
clearJobTypeToggle,
jobTypeCheck,
// ... 其他 actions
} = jobSlice.actions;
export default jobSlice.reducer;4. 在组件中触发请求(无需手动 dispatch)
// src/components/SearchFilters.tsx
import { useFetchCategoriesQuery, useFetchJobTypesQuery } from '../store/api';
export default function SearchFilters() {
const { data: categories, isLoading: catLoading } = useFetchCategoriesQuery();
const { data: jobTypes, isLoading: typeLoading } = useFetchJobTypesQuery();
if (catLoading || typeLoading) return <div>Loading filters...</div>;
return (
<div className="filters">
<h3>Category</h3>
{categories?.map((cat) => (
<label key={cat.id}>
<input
type="checkbox"
onChange={() => {/* 触发 jobTypeCheck action */}}
checked={/* 从 state.jobTypeList 读取 */}
/>
{cat.name}
</label>
))}
{/* 其他过滤器渲染逻辑... */}
</div>
);
}⚠️ 注意事项与最佳实践
- 不要在 initialState 中预设 API 数据:始终初始化为 [] 或 null,由 RTK Query 控制加载时机;
- 避免直接修改 state.xxx = payload 以外的字段:RTK Query 的 matchFulfilled 是唯一可信的数据源入口;
- 利用自动缓存与请求去重:相同参数的 useFetchCategoriesQuery() 多次调用不会重复请求;
- 错误与加载状态交由 Query Hook 管理:组件内用 isLoading/isError/error,而非在 reducer 中手动维护;
- 如需初始加载,可在根组件或路由守卫中调用:RTK Query 默认启用懒加载(skip: false),首次渲染即触发。
通过以上方式,你的搜索过滤器将真正“活”起来——数据来源可控、更新及时、结构统一,且完全符合 Redux 最佳实践。告别硬编码,拥抱声明式数据流。

















