[英]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{
~~
}
這非常復雜,以至於我什至都不認為我能正確解釋。 首先,我將你的基類改變的東西,只返回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>
,其中Q
是C
正確的鍵集。
然后我們創建將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>;
之所以要咖喱,是因為我們要推斷A
和C
但需要手動指定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.