Redux Toolkit中Slice间状态异常干扰问题求助
我用Redux Toolkit开发,项目里有多个独立维护状态的Slice(roles、jobs、users等)。触发getAllRoles异步Action时,roles Slice的loading状态能正确设为true,但jobs、users等其他Slice的loading也被错误设为true,且无法恢复为false;触发getJobs Action时,users Slice的loading同样会被异常修改。Redux DevTools显示roles/getAllRoles/pending Action会修改其他Slice的loading状态。试过移除addMatcher、给Action和Slice设唯一名称、拆分builder.addCase链式调用,问题仍未解决。
roles Slice代码
import { createSlice } from '@reduxjs/toolkit'; import { getAllRoles } from './roleActions'; const initialState = { roles: [], loading: false, isSuccess: false, message: '', }; const authReducer = createSlice({ name: 'roles', initialState, reducers: {}, extraReducers: (builder) => { builder .addCase(getAllRoles.pending, (state) => { state.isSuccess = false; state.message = ''; state.loading = true; }) .addCase(getAllRoles.fulfilled, (state, { payload }) => { state.roles = payload.data; state.message = payload.message; state.isSuccess = true; state.loading = false; }) .addCase(getAllRoles.rejected, (state, { payload }) => { state.isSuccess = false; state.message = payload; state.loading = false; }); }, }); export default authReducer.reducer;
getAllRoles Action代码
import { createAsyncThunk } from '@reduxjs/toolkit'; import API from '../../api'; import { formatError } from '@utils/index'; export const getAllRoles = createAsyncThunk('role/getAllRoles', async (_, { rejectWithValue }) => { try { const { data } = await API.get('/roles'); return data; } catch (error) { return rejectWithValue(formatError(error)); } });
调用方式
useEffect(() => { dispatch(getAllRoles()); }, [dispatch]);
jobs Slice代码
import { createSlice } from '@reduxjs/toolkit'; import { deleteJob, getJobs, updateJob, createJob } from './jobActions'; const initialValues = { data: { jobs: [], totalCount: 0, }, loading: false, isSuccess: false, message: '', }; const jobsReducer = createSlice({ name: 'jobs', initialState: initialValues, reducers: {}, extraReducers: (builder) => { // Get Jobs builder .addCase(getJobs.fulfilled, (state, { payload }) => { return { ...state, data: payload.data, message: payload.message, isSuccess: true, loading: false, }; }) .addCase(deleteJob.fulfilled, (state, { payload }) => { const updatedJobs = [...state.data.jobs.filter((job) => job._id !== payload)]; return { ...state, data: { totalCount: state.data.totalCount - 1, jobs: updatedJobs, }, message: 'Job deleted successfully', isSuccess: true, loading: false, }; }) .addCase(updateJob.fulfilled, (state, { payload }) => { const updatedJobs = [...state.data.jobs.filter((job) => job._id !== payload.data._id), payload.data]; return { ...state, data: { totalCount: state.data.totalCount, jobs: updatedJobs, }, message: payload.message, isSuccess: true, loading: false, }; }) .addCase(createJob.fulfilled, (state, { payload }) => { return { ...state, data: { totalCount: state.data.totalCount + 1, jobs: [payload.data, ...state.data.jobs], }, message: payload.message, isSuccess: true, loading: false, }; }) .addMatcher( (action) => action.type.endsWith('/pending'), (state) => ({ ...state, isSuccess: false, message: '', loading: true, }), ) .addMatcher( (action) => action.type.endsWith('/rejected'), (state, { payload }) => ({ ...state, isSuccess: false, message: payload, loading: false, }), ); }, }); export default jobsReducer.reducer;
users Slice代码
import { createUser, deleteUser, getUsers, updateUser } from './userActions'; const initialState = { users: [], loading: false, isSuccess: false, message: '', }; const usersReducer = createSlice({ name: 'users', initialState, reducers: {}, extraReducers: (builder) => { // Get Users builder.addCase(getUsers.fulfilled, (state, { payload }) => { state.loading = false; state.message = payload.message; state.users = payload.data; state.isSuccess = true; }); // Delete Users builder.addCase(deleteUser.fulfilled, (state, { payload }) => { state.loading = false; state.message = payload.message; state.users = [...state.users.filter((user) => user._id !== payload)]; state.isSuccess = true; }); // Update User builder.addCase(updateUser.fulfilled, (state, { payload }) => { state.loading = false; state.message = payload.message; state.users.splice( state.users.findIndex((user) => user.email === payload.data.email), 1, payload.data, ); state.isSuccess = true; state.message = payload.message; }); // Create User builder.addCase(createUser.fulfilled, (state, { payload }) => { state.loading = false; state.message = payload.message; state.users.unshift(payload.data); state.isSuccess = true; }); // Pending State builder.addMatcher( (action) => action.type.endsWith('/pending'), (state) => { state.isSuccess = false; state.message = ''; state.loading = true; }, ); // Rejected State builder.addMatcher( (action) => action.type.endsWith('/rejected'), (state, { payload }) => { state.isSuccess = false; state.message = payload; state.loading = false; }, ); }, }); export default usersReducer.reducer;
Store配置代码
import storage from 'redux-persist/lib/storage'; import { combineReducers } from 'redux'; import { persistReducer } from 'redux-persist'; import thunk from 'redux-thunk'; import { authReducer, usersReducer, rolesReducer, jobsReducer, surveysReducer, pageInfoReducer } from './features'; const reducers = combineReducers({ auth: authReducer, users: usersReducer, roles: rolesReducer, jobs: jobsReducer, survey: surveysReducer, pageInfo: pageInfoReducer, }); const store = configureStore({ reducer: reducers, devTools: process.env.NODE_ENV !== 'production', middleware: [thunk], }); export default store;
问题根源与修复方案
问题原因
jobs和users Slice里的addMatcher使用了action.type.endsWith('/pending')和action.type.endsWith('/rejected')的匹配规则,这会匹配所有异步Action的pending/rejected状态,不管这个Action属于哪个Slice。比如roles/getAllRoles/pending会触发jobs和users Slice里的pending matcher,导致它们的loading被错误设置为true。而且这两个Slice只处理了对应Action的fulfilled状态,没有处理pending和rejected,所以当其他Slice的Action完成时,它们的loading无法恢复。
修复步骤
- 为每个Slice的matcher添加前缀过滤:只匹配当前Slice所属的Action。比如jobs Slice只匹配以
jobs/开头的Action,users Slice只匹配以users/开头的Action。 - 确保Action的type前缀与Slice名称一致:createAsyncThunk的第一个参数要以对应Slice的名称开头,避免匹配混乱。
修改后的jobs Slice关键代码
// 替换原来的addMatcher .addMatcher( (action) => action.type.startsWith('jobs/') && action.type.endsWith('/pending'), (state) => ({ ...state, isSuccess: false, message: '', loading: true, }), ) .addMatcher( (action) => action.type.startsWith('jobs/') && action.type.endsWith('/rejected'), (state, { payload }) => ({ ...state, isSuccess: false, message: payload, loading: false, }), )
对应jobActions里的Action要修改前缀:
// jobActions示例 export const getJobs = createAsyncThunk('jobs/getJobs', async () => { // ...业务逻辑 });
修改后的users Slice关键代码
// 替换原来的addMatcher .addMatcher( (action) => action.type.startsWith('users/') && action.type.endsWith('/pending'), (state) => { state.isSuccess = false; state.message = ''; state.loading = true; }, ) .addMatcher( (action) => action.type.startsWith('users/') && action.type.endsWith('/rejected'), (state, { payload }) => { state.isSuccess = false; state.message = payload; state.loading = false; }, )
对应userActions里的Action要修改前缀:
// userActions示例 export const getUsers = createAsyncThunk('users/getUsers', async () => { // ...业务逻辑 });
- 可选优化:移除全局matcher,为每个Action单独处理pending/rejected
如果不想用matcher,也可以为每个异步Action单独添加pending和rejected的处理,这样更明确,避免匹配错误:
// users Slice示例 builder .addCase(getUsers.pending, (state) => { state.isSuccess = false; state.message = ''; state.loading = true; }) .addCase(getUsers.rejected, (state, { payload }) => { state.isSuccess = false; state.message = payload; state.loading = false; }) // 其他Action的pending/rejected同理
内容的提问来源于stack exchange,提问作者Muhammad Naeem

