You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.

1. First: Refactor Your State Structure for Better Organization

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.

2. Use a Helper Function to Cut Reducer Boilerplate

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;
  }
};
3. Go Even DRYer with a Custom Middleware

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.

4. The Modern Approach: Redux Toolkit

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:39:44