繁体   English   中英

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

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

我想在打字稿中制作方法链类。 (例如a.add(3).add(3).mul(3)、、、

但就我而言,根据语法指南,在方法之后只能访问部分方法

例如,在A方法之后,A和B方法可用。 在使用B方法之后,可以使用B和C方法。

我实现它就像跟随和成功。 type detect和linter可以很好地工作,但是我认为这不是实现的正确方法。 运行代码在这里

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);

因为我可能要关心每个方法的返回类型,因为类中可能有将近数百种方法

所以我想要的是在一个对象中管理所有方法语法(方法可用性?在方法之后可访问的方法列表?),并根据该语法生成​​类接口。

正如您在下面看到的那样,我尝试大致做我想要的。 但是,也许由于递归,这种类型不能正常工作:(是否有某种方法可以解决此问题?还是有很酷的方法可以满足我的要求?

我认为最好有一些类似“ Chain”的类型

类型a =类型链

//与我上面实现的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{
  ~~
}

在此处重新附加ABC类跑步代码的播放代码网址

这非常复杂,以至于我什至都不认为我能正确解释。 首先,我将你的基类改变的东西,只返回this你打算做环连接的方法。 在运行时,这基本上是相同的。 只是错误的编译时类型定义:

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;
  }
}

然后,我想描述如何将ABC构造函数解释为ChainABC构造函数,因为两者在运行时相同。

让我们提出一种确定类的可链接方法的方法……我将说它们只是那些函数值属性,这些属性返回的值与类实例的类型相同:

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

当您将ABC转换为ChainABC您将需要一个类型,该类型将此类可链接方法映射到其他可链接方法的并集,从而满足此约束:

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

最后,我们将描述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]
};

这是递归的...而且很复杂。 基本上ChainedClass<C, M, P>看起来像Pick<C, P>但是用C的可链接方法替换为返回ChainedClass<C, M, Q> ,其中QC正确的键集。

然后我们创建将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>;

之所以要咖喱,是因为我们要推断AC但需要手动指定M

这是我们的用法:

const ChainABC = constrainClass(ABC)<{ 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"} ,代表您想放置的约束。

测试它:

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

这样可行。 您会注意到,如果您检查Intellisense,您将在A() C()之后不能调用C() ,并且在B()之后不能调用A() ,并且在C()之后不能调用A()B() C()

好的,希望能有所帮助; 祝好运!

链接到代码

暂无
暂无

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

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