ngrx:通用缩减器

时间:2018-05-02 08:19:26

标签: angular typescript refactoring ngrx

我有一个应用程序,它有多个具有相同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,其中包含名为(clientListvehiculeList的属性, ...),属性名称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;
    }

}

1 个答案:

答案 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'),
}