我有一个应用程序,它有多个具有相同reducer功能的商店。我试图制作一个通用的减速机,它工作正常。
通用减速机设置:
interface State<T> {
itemList : T[]
}
const initialState: State<any> = {
itemList: []
}
const createReducer = <T>(type: string) => {
return <T>(state = initialState, action: any): State<T> => {
switch (action.type) {
case types.get(type).Add:
return {
...state,
itemList: [...state.itemList, action.payload]
}
case types.get(type).AddList:
return {
...state,
itemList: [...state.itemList, ...action.payload]
};
default:
return state;
}
}
}
然后我会将reducer组合如下:
export const reducers: ActionReducerMap<AppState> = {
vehiculeState: createReducer<Vehicule>('vehicule'),
rentState: createReducer<Rent>('rent'),
clientState : createReducer<Client>('client'),
companionState : createReducer<Client>('companion'),
paymentState : createReducer<Payment>('payment'),
notificationState : createReducer<Notification>('notification'),
employeeState : createReducer<Employee>('employee')
}
问题是,通过切换到通用模式,我将不得不重写我的应用程序的一大块,因为我已经创建了多个包含状态的Reducer,其中包含名为(clientList
,vehiculeList
的属性, ...),属性名称itemList
的信息量不大。所以我的问题是如何切换到通用模式并保持状态属性不变?
当前减速器的示例:
export function rentReducer(state = initialState, action: rentActions.RentActions): State {
switch(action.type){
case rentActions.ADD_RENT:
return{
...state,
rentList : [...state.rentList, action.payload]
}
case rentActions.ADD_RENT_LIST:
return {
...state,
rentList : [...state.rentList, ...action.payload]
};
default:
return state;
}
}
答案 0 :(得分:5)
您可以使用字符串来表示itemList
属性名称,并使用映射类型将该字符串转换为State
类型的类型安全属性。一个缺点是映射类型不支持使用扩展运算符,但我们可以使用Object.assign
// We add an extra parameter to State that will be the property name passed in as a string literal type
// So State<Vehicle, 'vehicleList'> will be a type equivalent to { vehicleList : Vehicle[] }
type State<T, TListName extends string> = {
[P in TListName] : T[]
}
// We create a function that creates the initial state by initializing an object with an empty array and the given property name
const initialState = <T, TListName extends string>(itemListName: TListName): State<T, TListName> => {
let result = {} as State<any, TListName>;
result[itemListName] = [];
return result;
};
// Since we can't use the spread operator, we create a new function that updates the state
// state will be the original state,
// itemListName the property name which contains the list
// newItems will be the new list
const updateState = <T, TListName extends string>(args: { state: State<T, TListName>, itemListName: TListName, newItems: T[] }): State<T, TListName> => {
return Object.assign({},args.state, {
[args.itemListName] : args.newItems
});
}
// We will use a 2 function approach for the createReducer function
// We do this in order to be able to specify the item type (T) explicitly,
// but not have to specify the string literal type TListName and let it be inferred
const createReducer = <T>(type: string) => <TListName extends string>(itemListName: TListName) => {
return (state = initialState<T, TListName>(itemListName), action: any): State<T, TListName> => {
switch (action.type) {
case types.get(type).Add:
return updateState({
state,
itemListName,
newItems: [...state[itemListName], <T>action.payload]
});
case types.get(type).AddList:
return updateState({
state,
itemListName,
newItems: [...state[itemListName], ...<T[]>action.payload]
})
default:
return state;
}
}
}
export const reducers = {
vehiculeState: createReducer<Vehicule>('vehicule')('vehiculeItems'),
rentState: createReducer<Rent>('rent')('rentItems'),
clientState : createReducer<Client>('client')('clientItems'),
companionState : createReducer<Client>('companion')('companionItems'),
paymentState : createReducer<Payment>('payment')('paymentItems'),
notificationState : createReducer<Notification>('notification')('notificationItems'),
employeeState : createReducer<Employee>('employee')('employeeItems'),
}