使用接口';TypeScript泛型中的属性?

使用接口';TypeScript泛型中的属性?,typescript,generics,redux,Typescript,Generics,Redux,我正在尝试创建一个帮助器通用actionCreator函数,以避免为我的每个操作创建actionCreator类型的函数: interface Response { readonly value: 'A' | 'B'; } enum ActionTypes { created = 'ns/created', deleted = 'ns/deleted', } interface Created { readonly type: ActionTypes.crea

我正在尝试创建一个帮助器通用
actionCreator
函数,以避免为我的每个操作创建actionCreator类型的函数:

interface Response {
    readonly value: 'A' | 'B';
}

enum ActionTypes {
    created = 'ns/created',
    deleted = 'ns/deleted',
}

interface Created {
    readonly type: ActionTypes.created;
    readonly payload: Response;
}

interface Deleted {
    readonly type: ActionTypes.deleted;
}

type Action = Created | Deleted;

// actionCreator = (type, payload) => ({type, payload});
// actionCreator(ActionTypes.created, response);
// actionCreator(ActionTypes.deleted);
是否可以告诉TS,
actionCreator
不应该只返回字符串和可选的有效负载对象,而是应该知道
type Action
以检查类型和有效负载对?
类似于
函数actionCreator(type:T.type,payload:T.payload)
的操作无效。

以下所有操作都假定您有两种以上的操作类型。如果您真的只有两个,那么使用函数重载并完成:

function actionCreator(type: ActionTypes.created, payload: Response): Created;
function actionCreator(type: ActionTypes.deleted): Deleted;
function actionCreator(type: any, payload?: any) {
  return (typeof payload === 'undefined') ? { type } : { type, payload }
}
有关更一般的内容,请继续阅读:


一旦该功能进入TypeScript 2.8(预计将于2018年3月发布),您现在就可以使用
typescript@next
)您可以指定一个功能,该功能的行为(我认为)与您想要的一样,而无需触摸现有代码,如下所示:

// If K is a key of T, return T[K].  Otherwise, return R
type MaybePropType<T, K extends string, R=never> = K extends keyof T ? T[K] : R

// Given K, one of the "type" properties of the Action union,
// produce the constituent of Action union whose type is K.  So:
// ActionFromType<ActionTypes.created> is Created, and
// ActionFromType<ActionTypes.deleted> is Deleted.
type ActionFromType<K extends Action['type'], AA = Action>
  = AA extends infer A
  ? MaybePropType<AA, 'type'> extends K ? AA : never
  : never

// The one-argument actionCreator() call accepts an element of ActionTypes
// corresponding to an Action with no payload, and produces an Action of that type.
function actionCreator<
  T extends ActionTypes & ('payload' extends keyof ActionFromType<T> ? never : {})
  >(type: T): ActionFromType<T>;

// The two-argument actionCreator() call accepts an element from ActionTypes
// corresponding to an Action with a payload, and a value of the payload type,
// and produces an Action of that type.
function actionCreator<
  T extends ActionTypes & ('payload' extends keyof ActionFromType<T> ? {} : never)
  >(type: T, payload: MaybePropType<ActionFromType<T>, 'payload'>): ActionFromType<T>;

// The implementation of actionCreator() creates a one-or-two-property
// value from its arguments.  Note that this implementation won't be right
// if you ever add new Action types that require more properties
function actionCreator(type: any, payload?: any) {
  return (typeof payload === 'undefined') ? { type } : { type, payload }
}
这需要进行大量的类型转换,一些错误消息将是神秘的,这取决于一个非常新的特性


如果您不介意更改您的代码,您可以得到一些与TypeScript 2.7兼容的东西,其中
actionCreator()
更容易推理。。。但之前的样板可能有点吓人:

// create a mapping from action type to payload type
type ActionPayloads = {
  'ns/created': Response
}
// create a list of actions with no payloads
type ActionsWithoutPayloads = 'ns/deleted';

// this is a helper type whose keys are the ActionTypes
// and whose values represent the part of the Action without the type
// so, just {} for a payloadless type, and {readonly payload: P} for
// a payloaded type
type ActionMap = { [K in ActionsWithoutPayloads]: {} } &
  { [K in keyof ActionPayloads]: { readonly payload: ActionPayloads[K] } }

// this is a helper function which makes sure we define ActionTypes
// correctly
const keysOfActionMap = <T extends { [k: string]: keyof ActionMap }>(x: T) => x;

// now we have ActionTypes.  It is a plain object, not an enum, 
// but it behaves similarly
const ActionTypes = keysOfActionMap({
  created: 'ns/created',
  deleted: 'ns/deleted'
});

// some helper type functions
type IntersectionToObject<T> = { [K in keyof T]: T[K] }
type ValueOf<T> = T[keyof T];

// extract Action from previous stuff.  
// Action by itself is a union of actions from before, while
// Action<K> is the action corresponding to K from ActionTypes.
type Action<K extends keyof ActionMap = keyof ActionMap> =
  ValueOf<{
    [P in K]: IntersectionToObject<{ readonly type: P } & ActionMap[P]>
  }>

// if you need names for Created and Deleted:    
type Created = Action<typeof ActionTypes.created>;
type Deleted = Action<typeof ActionTypes.deleted>;

唷!希望有帮助;祝你好运

// create a mapping from action type to payload type
type ActionPayloads = {
  'ns/created': Response
}
// create a list of actions with no payloads
type ActionsWithoutPayloads = 'ns/deleted';

// this is a helper type whose keys are the ActionTypes
// and whose values represent the part of the Action without the type
// so, just {} for a payloadless type, and {readonly payload: P} for
// a payloaded type
type ActionMap = { [K in ActionsWithoutPayloads]: {} } &
  { [K in keyof ActionPayloads]: { readonly payload: ActionPayloads[K] } }

// this is a helper function which makes sure we define ActionTypes
// correctly
const keysOfActionMap = <T extends { [k: string]: keyof ActionMap }>(x: T) => x;

// now we have ActionTypes.  It is a plain object, not an enum, 
// but it behaves similarly
const ActionTypes = keysOfActionMap({
  created: 'ns/created',
  deleted: 'ns/deleted'
});

// some helper type functions
type IntersectionToObject<T> = { [K in keyof T]: T[K] }
type ValueOf<T> = T[keyof T];

// extract Action from previous stuff.  
// Action by itself is a union of actions from before, while
// Action<K> is the action corresponding to K from ActionTypes.
type Action<K extends keyof ActionMap = keyof ActionMap> =
  ValueOf<{
    [P in K]: IntersectionToObject<{ readonly type: P } & ActionMap[P]>
  }>

// if you need names for Created and Deleted:    
type Created = Action<typeof ActionTypes.created>;
type Deleted = Action<typeof ActionTypes.deleted>;
// actions without payloads only accept one parameter
function actionCreator<K extends ActionsWithoutPayloads>(
  type: K): Action<K>;

// actions with payloads require two parameters
function actionCreator<K extends keyof ActionPayloads>(
  type: K, payload: ActionPayloads[K]): Action<K>;

// same impl as before
function actionCreator(type: any, payload?: any) {
  return (typeof payload === 'undefined') ? { type } : { type, payload }
}
declare const resp: Response;

// no errors
const created = actionCreator(ActionTypes.created, resp); // Created
const deleted = actionCreator(ActionTypes.deleted); // Deleted
const okayDeleted = actionCreator('ns/deleted'); // okay this time

// errors
const badCreated = actionCreator(ActionTypes.created); // missing payload
const badDeleted = actionCreator(ActionTypes.deleted, "payload?!"); // unexpected payload