简体   繁体   English

打字稿重复声明合并

[英]Typescript repetitive declaration merging

I am currently working on making a redux-like library with TypeScript. 我目前正在使用TypeScript制作类似redux的库。 The basic action looks like this: 基本动作如下所示:

interface ActionBase {
  type: string;
  payload: any;
}

I then extend the action interface for each action type. 然后,我为每种动作类型扩展动作接口。 For example, for a button-click event, I would have something like: 例如,对于按钮单击事件,我将具有以下内容:

interface ButtonClickAction extends ActionBase {
  type: 'BUTTON_CLICK';
  payload: {
    // Include some kind of metadata in here
  };
}

I then am adding some helper functions 然后,我添加了一些辅助功能

function isInstanceOfButtonClick(action: ActionBase ): action is ButtonClickAction {
  return action.type === 'BUTTON_CLICK';
}

function buildButtonClickAction(payload): ButtonClickAction {
  return {
    type: 'BUTTON_CLICK',
    payload,
  };
}

The problem is that I am doing this for over 20 different types of action. 问题是我正在执行20多种不同类型的操作。 Is there a dry-er way to do this? 有没有更干的方法可以做到这一点? For every action I need: 对于每个动作,我需要:

  1. The string value of the type ( "BUTTON_CLICK" ) 类型的字符串值( "BUTTON_CLICK"
  2. The type of the payload 有效载荷的类型
  3. The type of the action ( ButtonClickAction ) 动作类型( ButtonClickAction
  4. A builder funtion ( buildButtonClickAction ) 生成器功能( buildButtonClickAction
  5. An isInstance function ( isInstanceOfButtonClick ) 一个isInstance函数( isInstanceOfButtonClick

I can achieve all the concrete items (1, 4, 5) using a class or function, but I don't have a dry way to do 2 and 3. Right now, I have something like this for every action: 我可以使用类或函数来实现所有具体项目(1、4、5),但是我没有干的方法来完成2和3。现在,我对每个操作都具有类似的内容:

const KEY = 'BUTTON_CLICK';
namespace ButtonClick {
  export type Payload = {...}
  export interface Action extends ActionBase {
    type: typeof KEY;
    payload: Payload;
  }
}

let ButtonClick = makeActionValues<typeof KEY, ButtonClick.Payload, ButtonClick.Action>(KEY)

export default ButtonClick;

Is there a nicer way to do this? 有没有更好的方法可以做到这一点?

How about something like a function which creates a dictionary of Action factories, where each factory has its own isInstance() and buildAction() methods for the appropriate type of Action ? 像一个函数创建一个Action工厂字典的函数怎么样,其中每个工厂针对合适的Action类型都有自己的isInstance()buildAction()方法? Like this: 像这样:

interface ActionFactory<T extends string, P> {
  isInstance(action: Action): action is Action<T, P>;
  buildAction(payload: P): Action<T, P>;
}
interface Action<T extends string=string, P=any> {
  type: T,
  payload: P,
}

function getActionFactories<M extends object>(mappings: M): {[T in keyof M]: ActionFactory<T, M[T]>} {
  const ret: any = {};
  Object.keys(mappings).forEach((k: keyof M) => {
    type T = keyof M;
    type P = M[T];
    ret[k] = class Act {
      static isInstance(action: Action): action is Action<T, P> {
        return action.type === k;
      }
      static buildAction(payload: P): Action<T, P> {
        return new Act(payload);        
      }
      type: T = k;
      private constructor(public payload: P) { }
    }
  });
  return ret;
}

You use it by creating a mapping of action keys to payload types: 通过创建操作键到有效负载类型的映射来使用它:

const _: any = void 0;
const ActionPayloads = {
  ButtonClick: _ as { whatever: string },
  SomeOtherAction: _ as { parameter: number },
  WhoKnows: _ as { notMe: boolean },
}

The above is a bit ugly but it's the most DRY I could be... otherwise I'd need to specify the key names twice; 上面的代码有点丑陋,但这是我能做到的最干的……否则,我需要两次指定密钥名称; once for the payload mapping and once for the list of keys. 一次用于有效负载映射,一次用于密钥列表。 Then you call getActionFactories() : 然后调用getActionFactories()

const Actions = getActionFactories(ActionPayloads);

The resulting Actions object acts sort of like a namespace. 产生的Actions对象的行为有点像名称空间。 Observe: 观察:

const buttonClick = Actions.ButtonClick.buildAction({ whatever: 'hello' });
const someOtherAction = Actions.SomeOtherAction.buildAction({ parameter: 4 });
const whoKnows = Actions.WhoKnows.buildAction({ notMe: false });
const randomAction = Math.random() < 0.33 ? buttonClick : Math.random() < 0.5 ? someOtherAction : whoKnows

if (Actions.WhoKnows.isInstance(randomAction)) {
  console.log(randomAction.payload.notMe);
}

Does that work for you? 那对你有用吗?


Update 1 更新1

@darpa said : @darpa

I'd like to be able to get the type of the resulting action.payload 我希望能够得到所产生的类型action.payload

Well, to get the type of the payload for ButtonClick , you could use the ActionPayloads object and do something like: 好了,要获取ButtonClick的有效负载类型,可以使用ActionPayloads对象并执行以下操作:

const buttonClickPayload: typeof ActionPayloads.ButtonClick = {whatever: 'hello'};
const buttonClick = Actions.ButtonClick.buildAction(buttonClickPayload);

Or, if you want Actions to expose this type, you could add a phantom Payload property to ActionFactory : 或者,如果您希望Actions公开这种类型,则可以向ActionFactory添加一个虚拟的Payload属性:

interface ActionFactory<T extends string, P> {
  isInstance(action: Action): action is Action<T, P>;
  buildAction(payload: P): Action<T, P>;
  Payload: P; // phantom property
}

Then you could refer to the payload type like this: 然后,您可以像这样引用有效负载类型:

const buttonClickPayload: typeof Actions.ButtonClick.Payload = {whatever: 'hello'};
const buttonClick = Actions.ButtonClick.buildAction(buttonClickPayload);

But be careful not to actually try to use the value of Actions.ButtonClick.Payload , since it won't really exist: 但请注意不要实际尝试使用Actions.ButtonClick.Payload ,因为它实际上并不存在:

console.log(Actions.ButtonClick.Payload.whatever); // okay in TS but blows up at runtime.

Hope that helps! 希望有帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM