使用 Redux 中的
createAsyncThunk
+ createSlice
API,我正在为我的应用程序编写一个 ErrorSlice
,旨在监听应用程序中其他地方被拒绝的 thunk 并将错误添加到共享错误状态。
注意:(我没有在切片级别设置错误的原因是我的许多应用程序组件需要跨多个集合获取数据,并且跨多个切片处理错误比需要的更复杂)。
然而,有一种情况给我带来了问题。如果跨任何切片的任何 API 调用收到 401 NOT AUTHORIZED,我们需要调度
logout
thunk,这会清除本地状态,并将用户状态设置为默认 user/initialState。
在旧的 Redux 系统中,我们是这样处理的(这里提取了 Redux actions,reducer 被省略,因为它们基本上是样板文件):
export function handleError(dispatch, err, type) {
console.error('Invoking the error handler with the following error:', err);
if (err.error && err.error.message) {
switch (err.error.message) {
case 'Not Authorized':
dispatch(logout());
break;
default:
dispatch(addError({ message: err.error.message, type: type }));
}
return;
}
}
export function logout() {
return dispatch => {
return apiCall('delete', `/users/logout`)
.then(res => {
localStorage.clear();
dispatch(setCurrentUser(DEFAULT_STATE_CURRENT_USER.user));
})
.catch(err => {
handleError(dispatch, err, 'user');
throw err;
});
};
}
现在这是新的 ErrorSlice:
const errorSlice = createSlice({
name: 'errors',
initialState,
extraReducers(builder) {
builder
.addMatcher(
isAnyOf(
authUser.rejected,
updatePassword.rejected,
getUser.rejected,
updateUser.rejected,
forgotPassword.rejected,
verifyEmail.rejected,
resendVerificationEmail.rejected,
),
(state, action) => {
state.push({type: 'user', error: action.payload})
},
).addMatcher(
isAnyOf(
authUser.fulfilled,
updatePassword.fulfilled,
getUser.fulfilled,
updateUser.fulfilled,
forgotPassword.fulfilled,
verifyEmail.fulfilled,
resendVerificationEmail.fulfilled,
),
(state, action) => {
return state.filter(error => error?.type !== 'user');
},
)
问题是,由于您无法从reducers中分派thunk,并且您无法从thunk中侦听在其他切片中分派的操作,因此我没有找到一种方法来捕获此未授权场景并分派适当的操作。
您最好的选择可能是创建一个自定义侦听器中间件,它可以侦听这些特定的被拒绝操作并调度
logout
操作。
看
基本示例:
注销监听器
import { createListenerMiddleware, isAnyOf } from '@reduxjs/toolkit';
// action imports
const listenerMiddleware = createListenerMiddleware();
listenerMiddleware.startListening({
matcher: isAnyOf(
authUser.rejected,
forgotPassword.rejected,
getUser.rejected,
resendVerificationEmail.rejected,
updatePassword.rejected,
updateUser.rejected,
verifyEmail.rejected,
),
effect: (action, listenerApi) => {
listenerApi.dispatch(logout());
},
});
export default listenerMiddleware;
配置中间件后将其添加到您的商店,
import { configureStore } from '@reduxjs/toolkit';
// import reducers
import logoutListener from '../path/to/logoutListener';
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware()
.concat(logoutListener.middleware),
});
另一种选择是继续在每个能够调度注销操作的 thunk 中使用该
handleError
实用程序。
示例:
export function handleError(err, type, thunkApi) {
console.error('Invoking the error handler with the following error:', err);
if (err.error?.message) {
switch (err.error.message) {
case 'Not Authorized':
thunkApi.dispatch(logout());
break;
default:
thunkApi.dispatch(addError({
message: err.error.message,
type
}));
}
}
}
const authUser = createAsyncThunk(
"authUser",
async (arg, thunkApi) => {
...
try {
} catch(error) {
handleError(error, "user", thunkApi);
thunkApi.rejectWithValue(error);
}
},
);