[英]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.