Typescript :: 条件链接函数

Typescript :: Conditional chaining function

我正在尝试实现一组链式函数,但不知何故我卡在了这里。

interface ISimpleCalculator {
  plus(value: number): this;
  minus(value: number): this;
  divide(value: number): this;
  multiply(value: number): this;
  sum(): void
}

interface ISpecialCalculator extends ISimpleCalculator {
  specialPlus(value: number): ISimpleCalculator;
  specialMinus(value: number): ISimpleCalculator;
}

let testCalculator: ISpecialCalculator;
testCalculator  
  .plus(20)
  .multiply(2)
  .specialPlus(40)  
  .plus(20)
  .minus(5)
  .specialMinus(20)  //<-- Error! Property 'specialMinus' does not exist on type 'ISimpleCalculator'.
  .sum()

我想存档链中函数的类型检查。在上面的例子中,我希望 ISpecialCalculator 中的函数 specialPlusspecialMinus 只能使用一次,而 ISimpleCalculator 可以多次使用。我对打字稿很陌生,我一直在尝试不同的方法(高级类型(Pick & Omit)),但到目前为止都没有成功。我想知道在这种情况下是否还有其他方法可以提供帮助。

specialPlus(value: number): ISimpleCalculator;

当您调用此函数时,您将返回一个不再具有特殊功能的简单计算器。特殊接口也应该 return this 并且它应该工作:

interface ISpecialCalculator extends ISimpleCalculator {  
   specialPlus(value: number): this;
   specialMinus(value: number): this;
}

删除一些函数很简单,你可以使用 Omit<this, 'specialPlus'> 如果我们测试它几乎可以工作,如果你调用 specialPlus 如果你在另一个调用后立即调用它,你会得到一个错误specialPlus,但是您可以在调用 specialMinus

之后调用它
interface ISpecialCalculator extends ISimpleCalculator {
  specialPlus(value: number): Omit<this, 'specialPlus'>;
  specialMinus(value: number): Omit<this, 'specialMinus'>;
}

declare let testCalculator: ISpecialCalculator;
testCalculator  
  .specialPlus(40)
   // .specialPlus(40) // error 
  .specialMinus(20)
  .specialPlus(40) //ok 
  .sum()

Playground Link

这是因为 Omit 将在声明 testCalculator 时作用于 this 类型绑定,所以 specialMinus 实际上 return Omit<ISpecialCalculator, 'specialMinus'> 仍然包含 specialPlus,即使我们之前删除了它。我们想要的是 Omit 处理由前一个函数编辑的 this return 类型。如果我们使用泛型类型参数捕获每个调用的 this 的实际类型,并且来自此类型参数的 Omit 方法而不是来自多态 this.

的方法,我们就可以做到这一点
interface ISimpleCalculator {
  plus<TThis>(this: TThis,value: number): TThis;
  minus<TThis>(this: TThis,value: number): TThis;
  divide<TThis>(this: TThis,value: number): TThis;
  multiply<TThis>(this: TThis,value: number): TThis;
  sum(): void
}

interface ISpecialCalculator extends ISimpleCalculator {
  specialPlus<TThis>(this: TThis, value: number): Omit<TThis, 'specialPlus'>;
  specialMinus<TThis>(this: TThis, value: number): Omit<TThis, 'specialMinus'>;
}

declare let testCalculator: ISpecialCalculator;
testCalculator
  .specialPlus(40)
  // .specialPlus(40) // error 
  .specialMinus(20)
  .plus(10)
  .specialPlus(40) // also error 
  .plus(10)
  .sum()

Playground Link

尝试以下(根据问题测试的完整代码):

interface ISimpleCalculator {
  plus(value: number): this
  minus(value: number): this
  divide(value: number): this
  multiply(value: number): this
  sum(): void
}

interface ISpecialCalculator extends ISimpleCalculator {
  specialPlus(value: number): this
  specialMinus(value: number): this
}

let testCalculator: ISpecialCalculator
testCalculator
  .plus(20)
  .multiply(2)
  .specialPlus(40)
  .plus(20)
  .minus(5)
  .specialMinus(20) 
  .sum()

如果你想限制 special[Plus|Minus] 的使用,那么你可以在具体的 class 实现了 ISpecialCalculator 接口。

下面的代码可能会给你一些想法:

class Calculator implements ISpecialCalculator {
  specialPlusUsed = false
  specialMinusUsed = false

  specialPlus(value: number): this {
    if (this.specialPlusUsed) throw new Error("SpecialPlus can be used only once!")

    this.specialPlusUsed = true
    // Related calculations here...
    return this
  }

  specialMinus(value: number): this {
    if (this.specialMinusUsed) throw new Error("SpecialMinus can be used only once!")
    this.specialMinusUsed = true
    // Related calculations here...
    return this
  }

  plus(value: number): this {
    // Related calculations here...
    return this
  }

  minus(value: number): this {
    // Related calculations here...
    return this
  }

  divide(value: number): this {
    // Related calculations here...
    return this
  }

  multiply(value: number): this {
    // Related calculations here...
    return this
  }

  sum(): void {
    // Related calculations here...
  }
}