TypeScript中无需指定泛型的同步方法链式调用实现
TypeScript编译时同步链式调用:无需手动指定泛型的自动补全实现
能否在TypeScript中实现编译时同步方法链式调用,无需手动指定泛型类型仍能获得VS Code等编辑器的方法自动补全?
现有调用需要手动指定done方法的泛型参数:
const sdk = new MySDK(); sdk .foo('foo1').done<MySDK>() // 期望无需指定T,done()自动返回MySDK实例 .foo('foo2') .bar('bar1').done<Foo>() // 期望无需指定T,done()自动返回Foo实例 .baz('baz1').done<Foo>() // 期望无需指定T,done()自动返回Foo实例 .done<MySDK>() // 期望无需指定T,done()自动返回MySDK实例 .finalize();
理想的调用方式,无需手动指定泛型:
sdk .foo('foo1').done() .foo('foo2') .bar('bar1').done() .baz('baz1').done() .done() .finalize();
当前实现依赖运行时栈存储状态,但需要手动告知编译器done的返回类型,完整源码如下:
abstract class Creatable { creatableStack: MyObectType[]; name: string; constructor(creatableStack: MyObectType[], name: string) { console.log(creatableStack, name); this.creatableStack = creatableStack; this.name = name; this.creatableStack.push(this); } done<T>(): T { console.log('done', this.creatableStack); this.creatableStack.pop(); const previousMyObject = this.creatableStack.at(-1); return previousMyObject as T; } bar(name: string): Bar { return new Bar(this.creatableStack, name); } } type MyObectType = MySDK | Foo | Bar | Baz; class Bar extends Creatable { constructor(creatableStack: MyObectType[], name: string) { super(creatableStack, name); } } class Foo extends Creatable { constructor(creatableStack: MyObectType[], name: string) { super(creatableStack, name); } baz(name: string) { return new Baz(this.creatableStack, name); } } class Baz extends Creatable { constructor(creatableStack: MyObectType[], name: string) { super(creatableStack, name); } } class MySDK { creatableStack: MyObectType[] = []; constructor() { this.creatableStack.push(this); } foo(name: string): Foo { return new Foo(this.creatableStack, name); } finalize(): MySDK { console.log('finalize', this.creatableStack); return this; } } const sdk = new MySDK(); sdk .foo('foo1').done<MySDK>() // ideally .done() returns instance of MySDK w/o T .foo('foo2') .bar('bar1').done<Foo>() // ideally .done() returns instance of Foo w/o T .baz('baz1').done<Foo>() // ideally .done() returns instance of Foo w/o T .done<MySDK>() // ideally .done() returns instance of MySDK w/o T .finalize();
解决方案:通过泛型传递层级类型信息
核心思路是让每个Creatable子类在编译时就知道自己的上一层实例类型,通过泛型参数传递该信息,让done()方法自动返回正确的类型,无需手动指定泛型。
修改后的完整代码:
// 定义所有可创建类型的联合类型 type MyObjectType = MySDK | Foo | Bar | Baz; // Creatable类携带上一层类型的泛型参数 abstract class Creatable<PreviousType extends MyObjectType> { creatableStack: MyObjectType[]; name: string; constructor(creatableStack: MyObjectType[], name: string) { console.log(creatableStack, name); this.creatableStack = creatableStack; this.name = name; this.creatableStack.push(this); } // done方法直接返回泛型指定的上一层类型,无需手动传入 done(): PreviousType { console.log('done', this.creatableStack); this.creatableStack.pop(); const previousMyObject = this.creatableStack.at(-1); return previousMyObject as PreviousType; } bar(name: string): Bar<this> { return new Bar(this.creatableStack, name); } } // Bar类的上一层是Foo或其他Creatable子类,用泛型指定 class Bar<PreviousType extends MyObjectType> extends Creatable<PreviousType> { constructor(creatableStack: MyObjectType[], name: string) { super(creatableStack, name); } } // Foo类的上一层是MySDK class Foo extends Creatable<MySDK> { constructor(creatableStack: MyObjectType[], name: string) { super(creatableStack, name); } baz(name: string): Baz<this> { return new Baz(this.creatableStack, name); } } // Baz类的上一层是Foo class Baz<PreviousType extends Foo> extends Creatable<PreviousType> { constructor(creatableStack: MyObjectType[], name: string) { super(creatableStack, name); } } class MySDK { creatableStack: MyObjectType[] = []; constructor() { this.creatableStack.push(this); } foo(name: string): Foo { return new Foo(this.creatableStack, name); } finalize(): MySDK { console.log('finalize', this.creatableStack); return this; } } // 现在可以使用理想的调用方式,编辑器会自动补全方法 const sdk = new MySDK(); sdk .foo('foo1').done() // 返回MySDK,自动补全foo、finalize等方法 .foo('foo2') .bar('bar1').done() // 返回Foo,自动补全bar、baz等方法 .baz('baz1').done() // 返回Foo,自动补全bar、baz等方法 .done() // 返回MySDK,自动补全foo、finalize等方法 .finalize();
关键修改点:
- 让
Creatable类接受一个泛型参数PreviousType,代表当前实例的上一层实例类型; done()方法直接返回PreviousType,无需手动指定泛型;- 子类继承
Creatable时,明确指定自己的上一层类型:Foo继承Creatable<MySDK>,表示它的上一层是MySDK;Bar和Baz使用泛型继承,允许它们的上一层是任意合法的MyObjectType(比如Bar的上一层可以是Foo);
- 新增方法(如
bar、baz)返回子类时,传入this作为泛型参数,确保层级类型正确传递。
这样编译时TypeScript能自动推导done()的返回类型,编辑器也能提供准确的方法补全,同时保留原有的运行时栈逻辑。
内容的提问来源于stack exchange,提问作者mainframenzo
相关产品推荐
相关产品推荐

