Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript :: Conditional chaining function

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.

like image 388
Nelson Tang Avatar asked Dec 10 '19 07:12

Nelson Tang


People also ask

Does TypeScript have optional chaining?

In TypeScript, optional chaining is defined as the ability to immediately stop running an expression if a part of it evaluates to either null or undefined .

What is ?: operator in TypeScript?

TypeScript 3.7 added support for the ?? operator, which is known as the nullish coalescing operator. We can use this operator to provide a fallback value for a value that might be null or undefined .

What is Nullish coalescing in TypeScript?

The nullish coalescing operator is an alternative to || which returns the right-side expression if the left-side is null or undefined. In contrast, || uses falsy checks, meaning an empty string or the number 0 would be considered false.

Should I use optional chaining?

You can use optional chaining when attempting to call a method which may not exist. This can be helpful, for example, when using an API in which a method might be unavailable, either due to the age of the implementation or because of a feature which isn't available on the user's device.

What is optional chaining in typescript?

TypeScript optional chaining is a process of querying and calling properties, subscripts, and methods on optional that might be nil. ECMAScript feature allows developers to stop running if expressions encounter undefined or null values.

Why do we need conditional types in JavaScript?

At the heart of most useful programs, we have to make decisions based on input. JavaScript programs are no different, but given the fact that values can be easily introspected, those decisions are also based on the types of the inputs. Conditional types help describe the relation between the types of inputs and outputs.

What is typescript type inference used for?

// This example used the TypeScript type inference to provide a way to provide tooling to JavaScript patterns. For more examples on this: - example:code-flow

When should I use the operator in typescript?

There are a few different circumstances where you would consider using the operator when writing new TypeScript code: When you want to ACCESS a nested value, only if it exists (is not nullish). When you want to EXECUTE a function, only if it exists (is not nullish).


1 Answers

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()

Playground Link

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()

Playground Link

like image 124
Titian Cernicova-Dragomir Avatar answered Sep 17 '22 17:09

Titian Cernicova-Dragomir