Kako definirati funkciju koja prihvaća samo parametre koji mogu biti tip u TypeScriptu?


Želim definirati funkciju kao:

function isNumber(param: ???): param is number {
}

Ako parametar može biti broj, npr. number | string, number | boolean, može se proslijediti funkciji. Ako ne, npr. ako je njegov tip string ili boolean, doći će do grešaka pri kompajliranju.

Je li moguće definirati takav tip?

Odgovor #1

Konceptualno želite učiniti isNumber() generičkim u tipu T parametra param, ali želite da T bude donje ograničen tipom number. Ovo je suprotno uobičajenom extends ograničenju koje se smatra gornjom granicom. Nažalost, TypeScript izravno ne podržava generike s donjom granicom. To je zatraženo u microsoft/TypeScript#14520, i ako se ikada implementira, moglo bi koristiti super umjesto extends i izgledati kao

// NOT VALID TS (as of 4.7), DON'T DO THIS
function isNumber<T super number>(param: T): param is number {
  return typeof param === "number"
}

Ali, kao što sam rekao, ne možemo to učiniti.


Umjesto toga, možemo donekle oponašati generike s donjom granicom korištenjem standardne gornje granice na uvjetnom tipu. Nešto poput T extends (U extends T ? unknown : never) ponašat će se donekle poput T super U. Ako je T supertip U, tada (U extends T ? unknown : never) evaluira u unknown, i tako je ograničenje (T extends unknown) zadovoljeno. U suprotnom, (U extends T ? unknown : never) evaluira u never, i tako ograničenje (T extends never) vjerojatno nije zadovoljeno (osim ako T sama nije never, što se na ovaj način ne može spriječiti).

U vašem slučaju zapravo smo u redu ako je T ili supertip ili podtip number, pa možemo napisati T extends (number extends T ? unknown : number) i na taj način, ako je T slučajno numerički literali tipa poput 14, sve će i dalje raditi.

Problem je u tome što kompajler ne razumije da je param vjerojatno nadtip number, pa ne možete vratiti tip predikata param is number. Umjesto toga, trebate napisati tip koji kompajler vidi kao dodijeljiv T (i number). Zato ćemo koristiti presjek param is T & number.

Eto ga:

function isNumber<T extends number extends T ? unknown : number>(
  param: T
): param is number & T {
  return typeof param === "number"
}

A hajde da to isprobamo:

isNumber("oops"); // error, Argument of type 'string' is 
// not assignable to parameter of type 'number'.
isNumber(Math.random() < 0.5 ? "abc" : 123); // okay
isNumber(14); // okay

Izgleda dobro.

Poveznica na Playground za kod