You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 20:41:08