I'm trying to implement a set of chained function but somehow I get stuck here.
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()
I want to archive type check of the function in the chain. In the above example, I want the functions specialPlus
and specialMinus
in ISpecialCalculator
to be used once only and ISimpleCalculator
can be used for multiple times. I'm pretty fresh to the typescript and I've been trying different approaches (Advanced type ( Pick
& Omit
)) with no success so far. I want to know is there any other way to help in this case.
Removing the some functions is simple, you can just use Omit<this, 'specialPlus'>
If we test this it almost works, if you call specialPlus
you will get an error if you call it immediately after another call to specialPlus
, you can however call it after a call to 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()
This is because Omit
will work on the this
type bound when testCalculator
is declared, so specialMinus
will return in fact Omit<ISpecialCalculator, 'specialMinus'>
which will still contain specialPlus
even though we previously removed it. What we want is for Omit
to work on the type of this
returned by the previous function. We can do this if we capture the actual type of this
for each call using a generic type parameter, and Omit
methods from this type parameter not from polymorphic 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()
specialPlus(value: number): ISimpleCalculator;
When you call this function, you are getting back a simple calculator that doesn't have the special functions anymore. The special interface should also return this
and it should be working:
interface ISpecialCalculator extends ISimpleCalculator {
specialPlus(value: number): this;
specialMinus(value: number): this;
}
Try the following (full code tested based on the question):
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()
If you want to limit the special[Plus|Minus] usage then you can accomplish that in the concrete class that implements the ISpecialCalculator interface.
The code below may give you some ideas:
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...
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.