Redux Job Reducer中加载状态的DRY写法优化咨询
Great question—dealing with loading states in Redux without repeating yourself is a super common pain point, especially when you’ve got a bunch of async actions like your job application does. Let’s walk through some practical, DRY solutions that’ll clean up your code.
Your original approach of separate creating_job_loading-style fields gets messy fast. Instead, nest all loading states under a single loading object for cleaner organization:
const initialState = { loading: { creatingJob: false, fetchingJob: false, fetchingJobs: false, applyingJob: false, favJob: false }, // Add other state properties here: jobs, currentJob, etc. };
This structure is easier to extend and keeps related state grouped together.
Instead of writing duplicate spread operations for every action pair, create a reusable helper to update loading states based on action types:
// Helper to convert action types like "creating_job" to "creatingJob" const getLoadingKey = (actionType) => { return actionType.split('_').map((word, idx) => idx === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1) ).join('').replace('_job', '').replace('_jobs', ''); }; const updateLoading = (state, actionType, isLoading) => { const key = getLoadingKey(actionType); return { ...state, loading: { ...state.loading, [key]: isLoading } }; };
Then use this helper in your reducer to avoid repetitive code:
const jobReducer = (state = initialState, action) => { switch(action.type) { case 'creating_job': return updateLoading(state, action.type, true); case 'created_job': return updateLoading(state, action.type, false); case 'fetching_job': return updateLoading(state, action.type, true); case 'fetched_job': return updateLoading(state, action.type, false); // Repeat for applying/faving actions... // Handle data-updating actions here default: return state; } };
If you want to eliminate loading state cases entirely, build a simple middleware that automatically updates states based on your action naming pattern:
const loadingMiddleware = store => next => action => { // Match your async action patterns const isRequestAction = /(creating|fetching|applying|faving)_(job|jobs)/.test(action.type); const isSuccessAction = /(created|fetched|applied|faved)_(job|jobs)/.test(action.type); if (isRequestAction) { const key = getLoadingKey(action.type); store.dispatch({ type: 'UPDATE_LOADING', payload: { key, isLoading: true } }); } else if (isSuccessAction) { const key = getLoadingKey(action.type); store.dispatch({ type: 'UPDATE_LOADING', payload: { key, isLoading: false } }); } return next(action); };
Add one case to your reducer to handle the middleware’s action:
case 'UPDATE_LOADING': return { ...state, loading: { ...state.loading, [action.payload.key]: action.payload.isLoading } };
Now you don’t need to touch the reducer when adding new async actions—just follow your naming pattern, and the middleware handles loading states automatically.
If you’re open to upgrading your Redux setup, Redux Toolkit (the official recommended approach) solves this problem out of the box with createAsyncThunk and createSlice:
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; // Auto-generate pending/fulfilled/rejected actions for async calls export const createJob = createAsyncThunk('jobs/createJob', async (jobData) => { const res = await fetch('/api/jobs', { method: 'POST', body: JSON.stringify(jobData) }); return res.json(); }); export const fetchJob = createAsyncThunk('jobs/fetchJob', async (jobId) => { const res = await fetch(`/api/jobs/${jobId}`); return res.json(); }); const jobSlice = createSlice({ name: 'jobs', initialState: { loading: { createJob: false, fetchJob: false, fetchJobs: false, applyingJob: false, favJob: false }, jobs: [], currentJob: null }, extraReducers: (builder) => { builder .addCase(createJob.pending, (state) => { state.loading.createJob = true; }) .addCase(createJob.fulfilled, (state, action) => { state.loading.createJob = false; state.jobs.push(action.payload); }) .addCase(createJob.rejected, (state) => { state.loading.createJob = false; }) .addCase(fetchJob.pending, (state) => { state.loading.fetchJob = true; }) .addCase(fetchJob.fulfilled, (state, action) => { state.loading.fetchJob = false; state.currentJob = action.payload; }) // Add other thunks here... } }); export default jobSlice.reducer;
Redux Toolkit eliminates manual action types and spread operations, making loading state management clean and scalable.
内容的提问来源于stack exchange,提问作者Alisa F. Kennedy

