Typescript 2.8+:如何捕获动作创建者并将其映射到动作处理程序?

时间:2018-06-01 06:13:41

标签: typescript redux

我有以下动作创作者:

/** simply captures the string literal type */
function actionType<T extends string, U>(type: T, u: U) {
  return Object.assign({}, { type }, u);
}

// ------------------------------------
// Action Creators
// ------------------------------------
const ACTION_CREATORS = {
  toggleFilter: (params: { filterValueKey: string; filterKey: string; filterValue: string }) => {
    const { filterKey, filterValue, filterValueKey } = params;
    return actionType('FILTERS_TOGGLE', {
      payload: {
        filterValueKey,
        filterKey,
        filterValue
      }
    });
  },
  deleteFilter: (params: { filterValueKey: string }) => {
    const { filterValueKey } = params;
    return actionType('FILTERS_DELETE', {
      payload: {
        filterValueKey
      }
    });
  },
  searchFilterValues: (params: { activeFilterKey: string; query: string }) => {
    const { query, activeFilterKey } = params;
    return actionType('FILTERS_QUERY_VALUES', {
      payload: {
        query,
        activeFilterKey
      }
    });
  }
};

我想写的是

type ActionHandlers<T extends {
  [actionCreator: string]: (...args: any[]) => { type: string }
}> = { /* ... */ };

将为&#34;动作处理程序&#34;添加正确的输入。对象如下:

const ACTION_HANDLERS: ActionHandlers<typeof ACTION_CREATORS > = {
  FILTERS_TOGGLE: (state, action) => {
    action.payload.filterValueKey // this key is typed
    const newState = { ...state };
    return newState;
  },
  FILTERS_DELETE: () => {} /* ... */,
  FILTERS_QUERY_VALUES: (state, action) => {
    action.payload.query // this is also typed and typed differently
    const newState = { ...state };
    return newState;
  }
}

到目前为止,我能够捕获动作类型的名称并抓取动作创建者的返回类型,但我仍然坚持这一点:

type Action<T extends { type: string }> = T;

type ActionCreator<T extends string> = (...params: any[]) => { type: T };
type MapToNames<T extends { [key: string]: ActionCreator<any> }> = {
  [P in keyof T]: T[P] extends ActionCreator<infer U> ? U : never
};
type ActionNames<T extends { [key: string]: ActionCreator<any> }> = MapToNames<
  T
>[keyof MapToNames<T>];

type RemoveFunctions<T extends { [key: string]: (...args: any[]) => { type: string } }> = {
  [P in keyof T]: T[P] extends (...params: any[]) => Action<infer U> ? U : never
};

type Names = ActionNames<typeof ACTION_CREATORS>;
// type Names = "FILTERS_TOGGLE" | "FILTERS_DELETE" | "FILTERS_QUERY_VALUES"
type NoFunctions = RemoveFunctions<typeof ACTION_CREATORS>;

任何想法都将不胜感激!

1 个答案:

答案 0 :(得分:1)

这是一种可能的解决方案:

type GetActionCreatorTypes<T extends { [key: string]: (...args: any[]) => { type: string } }> = {
    // Get the return type of the action creator
    [P in keyof T]: T[P] extends (...params: any[]) => Action<infer U> ? U : never
}[keyof T]; // Make a union of all action types 


type ActionHandlers<T extends {
    [actionCreator: string]: (...args: any[]) => { type: string }
}> = {
        // Take all properties named type (will be a union of "FILTERS_TOGGLE" | "FILTERS_DELETE" | "FILTERS_QUERY_VALUES" in our case)
        [P in GetActionCreatorTypes<T>['type']]: (
            state: any, 
            // Type the action as beeing the only type in the union with the type property named P
            action: Extract<GetActionCreatorTypes<T>, { type: P }>
        ) => any 
    };


const ACTION_HANDLERS: ActionHandlers<typeof ACTION_CREATORS> = {
    FILTERS_TOGGLE: (state, action) => {
        action.payload.filterValueKey // ok 
        const newState = { ...state };
        return newState;
    },
    FILTERS_DELETE: () => { } /* ... */,
    FILTERS_QUERY_VALUES: (state, action) => {
        action.payload.query // this is also ok
        const newState = { ...state };
        return newState;
    }
}

游乐场link