简体   繁体   English

如何在打字稿中声明递归类型以进行方法链接?

[英]How to declare recursive types in typescript for method chaining?

I want to make method chain class in typescript. 我想在打字稿中制作方法链类。 (like a.add(3).add(3).mul(3),,,,) (例如a.add(3).add(3).mul(3)、、、

But in my case, only part of methods are accessible after method according to the syntax guide 但就我而言,根据语法指南,在方法之后只能访问部分方法

In example, after A method, A and B method is available. 例如,在A方法之后,A和B方法可用。 and after B method, B and C method is available. 在使用B方法之后,可以使用B和C方法。

I implement it like following and success. 我实现它就像跟随和成功。 the type detect and linter are work very well, but I don't think it is the right way to implement. type detect和linter可以很好地工作,但是我认为这不是实现的正确方法。 running code is in here 运行代码在这里

class ABC {
  private log: string[]
  public constructor() {
    this.log = [];

    this.A = this.A.bind(this);
    this.B = this.B.bind(this);
    this.C = this.C.bind(this);
    this.getLog = this.getLog.bind(this);
  }

  public A() {
    this.log.push('A');
    return { A: this.A, B: this.B, getLog: this.getLog };
  }

  public B() {
    this.log.push('B');
    return { B: this.B, C: this.C, getLog: this.getLog };
  }

  public C() {
    this.log.push('C');
    return { C: this.C, getLog: this.getLog };
  }

  public getLog() {
    return this.log;
  }
}

const abc = new ABC();
const log = abc.A().A().A().B().B().C().getLog();
console.log('log: ', log);

Cause I have to care about return type for every single method since there MIGHT be almost hundreds of methods in class 因为我可能要关心每个方法的返回类型,因为类中可能有将近数百种方法

So what I want is manage all method syntaxes(method availability? list of method accessible after method?) in one single object, and generate class interface according to that syntax. 所以我想要的是在一个对象中管理所有方法语法(方法可用性?在方法之后可访问的方法列表?),并根据该语法生成​​类接口。

As you can see in below, I tried to make roughly what I want. 正如您在下面看到的那样,我尝试大致做我想要的。 But maybe because of recursion, this type do not worke correctly :( Is there some way to solve this? Or is there cool way to provide my requirement? 但是,也许由于递归,这种类型不能正常工作:(是否有某种方法可以解决此问题?还是有很酷的方法可以满足我的要求?

I think it will be best to have some type 'Chain' like 我认为最好有一些类似“ Chain”的类型

type a = type Chain 类型a =类型链

//is same as the type of class ABC that I implemented above //与我上面实现的ABC类的类型相同

// information of function state machine
const Syntax = {
  A: {
    next: ['A', 'B'] as const,
  },
  B: {
    next: ['B', 'C'] as const,
  },
  C: {
    next: ['C'] as const,
  },
};

interface Chain {
  A(): Pick<Chain, typeof Syntax.A.next[number]>;
  B(): Pick<Chain, typeof Syntax.B.next[number]>;
  C(): Pick<Chain, typeof Syntax.C.next[number]>;
  getLog(): string;
}

class ChainABC implements Chain{
  ~~
}

Re-Attach play code url for class ABC running code is in here 在此处重新附加ABC类跑步代码的播放代码网址

This is complicated enough that I don't even think I can explain it properly. 这非常复杂,以至于我什至都不认为我能正确解释。 First, I will change your base class to something that returns just this for the methods you intend to make chainable. 首先,我将你的基类改变的东西,只返回this你打算做环连接的方法。 At runtime this is essentially the same; 在运行时,这基本上是相同的。 it's only the compile-time type definitions that are wrong: 只是错误的编译时类型定义:

class ABC {
  private log: string[] = [];

  public A() {
    this.log.push("A");
    return this;
  }

  public B() {
    this.log.push("B");
    return this;
  }

  public C() {
    this.log.push("C");
    return this;
  }

  public getLog() {
    return this.log;
  }
}

Then I want to describe how to interpret the ABC constructor as a ChainABC constructor, since both are the same at runtime. 然后,我想描述如何将ABC构造函数解释为ChainABC构造函数,因为两者在运行时相同。

Let's come up with a way to determine the chainable methods of a class... I'll say they are just those function-valued properties which return a value of the same type as the class instance: 让我们提出一种确定类的可链接方法的方法……我将说它们只是那些函数值属性,这些属性返回的值与类实例的类型相同:

type ChainableMethods<C extends object> = {
  [K in keyof C]: C[K] extends (...args: any) => C ? K : never
}[keyof C];

And when you turn ABC into ChainABC you will need a type which maps such chainable methods to a union of other chainable methods, which meets this constraint: 当您将ABC转换为ChainABC您将需要一个类型,该类型将此类可链接方法映射到其他可链接方法的并集,从而满足此约束:

type ChainMap<C extends object> = Record<
  ChainableMethods<C>,
  ChainableMethods<C>
>;

Finally we will describe ChainedClass<C, M, P> where C is the class type to modify, M is the method chaining map, and P is the particular keys we'd like to have exist on the result: 最后,我们将描述ChainedClass<C, M, P> ,其中C是要修改的类类型, M是方法链接映射, P是我们希望在结果上存在的特定键:

type ChainedClass<
  C extends object,
  M extends ChainMap<C>,
  P extends keyof C
> = {
  [K in P]: C[K] extends (...args: infer A) => C
    ? (
        ...args: A
      ) => ChainedClass<
        C,
        M,
        | Exclude<keyof C, ChainableMethods<C>>
        | (K extends keyof M ? M[K] : never)
      >
    : C[K]
};

This is recursive... and complicated. 这是递归的...而且很复杂。 Basically ChainedClass<C, M, P> looks like Pick<C, P> but with the chainable methods in C replaced by methods that return ChainedClass<C, M, Q> where Q is the correct set of keys from C . 基本上ChainedClass<C, M, P>看起来像Pick<C, P>但是用C的可链接方法替换为返回ChainedClass<C, M, Q> ,其中QC正确的键集。

Then we make the function that turns the ABC constructor into the ChainABC constructor: 然后我们创建将ABC构造函数转换为ChainABC构造函数的函数:

const constrainClass = <A extends any[], C extends object>(
  ctor: new (...a: A) => C
) => <M extends ChainMap<C>>() =>
  ctor as new (...a: A) => ChainedClass<C, M, keyof C>;

It's curried because we want to infer A and C but need to manually specify M . 之所以要咖喱,是因为我们要推断AC但需要手动指定M

Here's how we use it: 这是我们的用法:

const ChainABC = constrainClass(ABC)<{ A: "A" | "B"; B: "B" | "C"; C: "C" }>();

See how the M type is {A: "A" | "B"; B: "B" | "C"; C: "C"} 看看M类型是{A: "A" | "B"; B: "B" | "C"; C: "C"} {A: "A" | "B"; B: "B" | "C"; C: "C"} {A: "A" | "B"; B: "B" | "C"; C: "C"} , representing the constraint you wanted to place, I think. {A: "A" | "B"; B: "B" | "C"; C: "C"} ,代表您想放置的约束。

Testing it: 测试它:

const o = new ChainABC()
  .A()
  .A()
  .A()
  .B()
  .B()
  .C()
  .C()
  .getLog();

That works. 这样可行。 You will notice if you inspect the Intellisense that you can't call C() after A() and you can't call A() after B() and you can't call A() or B() after C() . 您会注意到,如果您检查Intellisense,您将在A() C()之后不能调用C() ,并且在B()之后不能调用A() ,并且在C()之后不能调用A()B() C()

All right, hope that helps; 好的,希望能有所帮助; good luck! 祝好运!

Link to code 链接到代码

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

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