将具有完全相同代码的 extraReducers 与 redux 工具包结合起来
combine extraReducers with exact same code with redux toolkit
我使用 redux 工具包创建了一个全局错误处理切片。我想重构它,让它更“干”:
const errorsSlice = createSlice({
name: "error",
initialState,
reducers: {
clearError: (state) => {
state.errors = null;
state.isOpen = false;
},
},
extraReducers: {
[createScript.rejected]: (state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
},
[createScene.rejected]: (state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
},
},
});
2 个 extraReducer 做完全相同的事情并且有效负载被归一化。我的代码可以正常工作。
有没有办法将 2“组合”到一个 extraReducer(最后会更多)?
我认为没有办法将两个 case reducer 组合成一个 case reducer,但您当然可以为每个提供相同的 reducer 功能。将重复的 reducer 函数重构为一个通用的 reducer 函数。
const rejectionReducer = (state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
};
...
const errorsSlice = createSlice({
name: "error",
initialState,
reducers: {
clearError: (state) => {
state.errors = null;
state.isOpen = false;
},
},
extraReducers: {
[createScript.rejected]: rejectionReducer,
[createScene.rejected]: rejectionReducer,
});
更新
使用 isRejectedWithValue
高阶函数,您可以将 thunk 动作组合到匹配器中。
import { isRejectedWithValue } from '@reduxjs/toolkit';
const rejectionReducer = (state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
};
const errorsSlice = createSlice({
name: "error",
initialState,
reducers: {
clearError: (state) => {
state.errors = null;
state.isOpen = false;
},
},
extraReducers: builder => {
builder.addMatcher(
isRejectedWithValue(createScript, createScene), // <-- thunk actions
rejectionReducer
);
},
});
您目前正在用 "Map Object" notation. You want to use "Builder Callback" notation 定义您的 extraReducers
。
使用构建器回调,您可以使用 .addCase()
匹配单个操作,但您也可以使用 .addMatcher()
处理多个操作。 addMatcher()
的第一个参数是一个函数,它接受 action
和 returns 和 boolean
是否匹配。这里我们要匹配所有以'/rejected'
.
结尾的动作
const errorsSlice = createSlice({
name: "error",
initialState,
reducers: {
clearError: (state) => {
state.errors = null;
state.isOpen = false;
}
},
extraReducers: (builder) =>
builder.addMatcher(
// matcher function
(action) => action.type.endsWith("/rejected"),
// case reducer
(state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
}
)
});
你可以使用我的帮手:
import { AnyAction, AsyncThunk } from '@reduxjs/toolkit';
enum AsyncActionStatusesEnum {
PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected',
}
//eslint-disable-next-line
type GenericAsyncThunk = AsyncThunk<any, any, any>;
type PendingAction = ReturnType<GenericAsyncThunk[AsyncActionStatusesEnum.PENDING]>;
type FulfilledAction = ReturnType<GenericAsyncThunk[AsyncActionStatusesEnum.FULFILLED]>;
type RejectedAction = ReturnType<GenericAsyncThunk[AsyncActionStatusesEnum.REJECTED]>;
// gets a list of asynchronous actions and checks them for the status of at least one === 'pending'
export function isSomeAsyncActionsPending(matchedActionTypes: GenericAsyncThunk[]) {
return (action: AnyAction): action is PendingAction =>
matchedActionTypes
.map(actionType => `${actionType.typePrefix}/${AsyncActionStatusesEnum.PENDING}`)
.some(actionType => action.type.endsWith(actionType));
}
// gets a list of asynchronous actions and checks them for the status of at least one === 'fulfilled'
export function isSomeAsyncActionsFulfilled(matchedActionTypes: GenericAsyncThunk[]) {
return (action: AnyAction): action is FulfilledAction =>
matchedActionTypes
.map(actionType => `${actionType.typePrefix}/${AsyncActionStatusesEnum.FULFILLED}`)
.some(actionType => action.type.endsWith(actionType));
}
// gets a list of asynchronous actions and checks them for the status of at least one === 'rejected'
export function isSomeAsyncActionsRejected(matchedActionTypes: GenericAsyncThunk[]) {
return (action: AnyAction): action is RejectedAction =>
matchedActionTypes
.map(actionType => `${actionType.typePrefix}/${AsyncActionStatusesEnum.REJECTED}`)
.some(actionType => action.type.endsWith(actionType));
}
使用示例:
const fetchCoockBookList = createAsyncThunk(
'dataSlice/fetchCookBookList',
async (data: CoockBookParamsType) => await getCoockBook(data)
);
const fetchSeriesList = createAsyncThunk(
'dataSlice/fetchSeriesList',
async (data: SeriesParamsType) => await getSeries(data)
);
const isActionsPending = isSomeAsyncActionsPending([
fetchDataList,
fetchSeriesList,
]);
const dataSlice = createSlice({
// name, reducers, initialState, ...
extraReducers: builder => {
// others builder addCases, addMatchCases, ...
builder
.addMatcher(isActionsPending, state => {
state.error = null;
state.isLoading = true;
})
// addMatchers for fullfield, rejected
}
})
我使用 redux 工具包创建了一个全局错误处理切片。我想重构它,让它更“干”:
const errorsSlice = createSlice({
name: "error",
initialState,
reducers: {
clearError: (state) => {
state.errors = null;
state.isOpen = false;
},
},
extraReducers: {
[createScript.rejected]: (state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
},
[createScene.rejected]: (state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
},
},
});
2 个 extraReducer 做完全相同的事情并且有效负载被归一化。我的代码可以正常工作。
有没有办法将 2“组合”到一个 extraReducer(最后会更多)?
我认为没有办法将两个 case reducer 组合成一个 case reducer,但您当然可以为每个提供相同的 reducer 功能。将重复的 reducer 函数重构为一个通用的 reducer 函数。
const rejectionReducer = (state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
};
...
const errorsSlice = createSlice({
name: "error",
initialState,
reducers: {
clearError: (state) => {
state.errors = null;
state.isOpen = false;
},
},
extraReducers: {
[createScript.rejected]: rejectionReducer,
[createScene.rejected]: rejectionReducer,
});
更新
使用 isRejectedWithValue
高阶函数,您可以将 thunk 动作组合到匹配器中。
import { isRejectedWithValue } from '@reduxjs/toolkit';
const rejectionReducer = (state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
};
const errorsSlice = createSlice({
name: "error",
initialState,
reducers: {
clearError: (state) => {
state.errors = null;
state.isOpen = false;
},
},
extraReducers: builder => {
builder.addMatcher(
isRejectedWithValue(createScript, createScene), // <-- thunk actions
rejectionReducer
);
},
});
您目前正在用 "Map Object" notation. You want to use "Builder Callback" notation 定义您的 extraReducers
。
使用构建器回调,您可以使用 .addCase()
匹配单个操作,但您也可以使用 .addMatcher()
处理多个操作。 addMatcher()
的第一个参数是一个函数,它接受 action
和 returns 和 boolean
是否匹配。这里我们要匹配所有以'/rejected'
.
const errorsSlice = createSlice({
name: "error",
initialState,
reducers: {
clearError: (state) => {
state.errors = null;
state.isOpen = false;
}
},
extraReducers: (builder) =>
builder.addMatcher(
// matcher function
(action) => action.type.endsWith("/rejected"),
// case reducer
(state, { payload }) => {
const errorArray = Object.values(payload.message).map(
(key) => key.message
);
state.errors = errorArray;
state.isOpen = true;
}
)
});
你可以使用我的帮手:
import { AnyAction, AsyncThunk } from '@reduxjs/toolkit';
enum AsyncActionStatusesEnum {
PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected',
}
//eslint-disable-next-line
type GenericAsyncThunk = AsyncThunk<any, any, any>;
type PendingAction = ReturnType<GenericAsyncThunk[AsyncActionStatusesEnum.PENDING]>;
type FulfilledAction = ReturnType<GenericAsyncThunk[AsyncActionStatusesEnum.FULFILLED]>;
type RejectedAction = ReturnType<GenericAsyncThunk[AsyncActionStatusesEnum.REJECTED]>;
// gets a list of asynchronous actions and checks them for the status of at least one === 'pending'
export function isSomeAsyncActionsPending(matchedActionTypes: GenericAsyncThunk[]) {
return (action: AnyAction): action is PendingAction =>
matchedActionTypes
.map(actionType => `${actionType.typePrefix}/${AsyncActionStatusesEnum.PENDING}`)
.some(actionType => action.type.endsWith(actionType));
}
// gets a list of asynchronous actions and checks them for the status of at least one === 'fulfilled'
export function isSomeAsyncActionsFulfilled(matchedActionTypes: GenericAsyncThunk[]) {
return (action: AnyAction): action is FulfilledAction =>
matchedActionTypes
.map(actionType => `${actionType.typePrefix}/${AsyncActionStatusesEnum.FULFILLED}`)
.some(actionType => action.type.endsWith(actionType));
}
// gets a list of asynchronous actions and checks them for the status of at least one === 'rejected'
export function isSomeAsyncActionsRejected(matchedActionTypes: GenericAsyncThunk[]) {
return (action: AnyAction): action is RejectedAction =>
matchedActionTypes
.map(actionType => `${actionType.typePrefix}/${AsyncActionStatusesEnum.REJECTED}`)
.some(actionType => action.type.endsWith(actionType));
}
使用示例:
const fetchCoockBookList = createAsyncThunk(
'dataSlice/fetchCookBookList',
async (data: CoockBookParamsType) => await getCoockBook(data)
);
const fetchSeriesList = createAsyncThunk(
'dataSlice/fetchSeriesList',
async (data: SeriesParamsType) => await getSeries(data)
);
const isActionsPending = isSomeAsyncActionsPending([
fetchDataList,
fetchSeriesList,
]);
const dataSlice = createSlice({
// name, reducers, initialState, ...
extraReducers: builder => {
// others builder addCases, addMatchCases, ...
builder
.addMatcher(isActionsPending, state => {
state.error = null;
state.isLoading = true;
})
// addMatchers for fullfield, rejected
}
})