簡體   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