Împachetarea unei funcții Typescript suprasolicitate


Am probleme să scriu un înveliș pentru o funcție suprasolicitată.

Funcția pe care o înfășor este funcția de trimitere a emailurilor SendGrid. Acea funcție este supraîncărcată astfel (simplificat):

declare class MailService {
  send(data: Email): foo;
  send(data: Email[]): foo;
}

Vreau să scriu o funcție care poate accepta fie un Email, fie un Email[]. Dar când fac asta astfel, apare o eroare care spune că niciunul dintre suprasarcini nu corespunde apelului:

function sendEmailWrapper1(data: Email | Email[]) {
  sendGrid.send(data)
}

Totuși, asta funcționează:

function sendEmailWrapper2(data: Email | Email[]) {
  if ('length' in data) {
    sendGrid.send(data)
  } else {
    sendGrid.send(data)
  }
}

Cred că ceea ce se întâmplă este că Email | Email[] nu este un input valid pentru send(data: Email) sau pentru send(data: Email[]), iar Typescript nu realizează că suprasarcinile înseamnă că sendGrid.send(data) ar trebui să fie valid (și, într-adevăr, dacă ramurile if și else pot avea același cod și sunt valide, asta face să pară că Typescript ar trebui să poată realiza că declarația este validă chiar și fără o declarație if care să ajute la deducerea unui tip, deoarece Typescript este doar la compilare și nu va direcționa efectiv execuția codului la runtime pentru a face apeluri diferite de funcții în funcție de tipuri).

Cele două întrebări ale mele:

  1. Care este cea mai bună practică în situații ca acestea?
  2. Este aceasta doar o deficiență în compilatorul Typescript, sau există un motiv întemeiat pentru care sendEmailWrapper1 ar eșua la compilare, dar sendEmailWrapper2 ar funcționa?

Mulțumesc!

Răspuns #1

Aceasta este o deficiență a declarației class MailService. Ar putea avea o definiție de tip mai precisă, astfel:

declare class MailService {
  send(data: Email | Email[]): foo;
}

Cea mai bună practică, după părerea mea, este să mărești definiția tipului local. Apoi să trimiți o cerere de pull către depozit cu modificarea.

De asemenea, acesta este un înveliș care folosește parametri rest. Va funcționa cu mai puțin efort din partea ta:

// Accepts on or more MailData objects.
// sendEmailWrapper3(data);
// sendEmailWrapper3(data, data1);
function sendEmailWrapper3(...data: MailData[]) {
  sendGrid.send(data);
}

Răspunsul #2

De fapt, această utilizare nu este un exemplu bun de suprasarcină, dacă trebuie să suprasolicitați metodele, înseamnă că trebuie să returnați tipuri diferite atunci când tipul parametrilor se schimbă. Dacă aveți nevoie de tipuri de returnare diferite, atunci suprasolicitarea metodelor este mai consecventă. De exemplu :

send(data: Email): foo; 
send(data: Email[]): foo[]

sau dacă parametrii au o proprietate opțională, dar când proprietatea este setată, atunci o altă proprietate trebuie să fie obligatorie.

function bar(params:{
    prop1: number;
    prop2?: boolean;
}): Foo;
function bar(params:{
    prop1: number;
    prop2: boolean;
    prop3: string;
}): Foo;

Din documentația TypeScript despre Supraincarcări:

JavaScript este în mod inerent un limbaj foarte dinamic. Nu este neobișnuit ca o singură funcție JavaScript să returneze tipuri diferite de obiecte în funcție de forma argumentelor transmise.