angular - Nu se poate scrie o metodă de extensie pentru Map<TKey, TValue[]> în TypeScript


Sunt un utilizator nou de Angular/TS și mă bucur să folosesc metodele de extensie. Funcționau bine pe tipurile standard, dar acum încerc să scriu una pentru Map unde V va fi un array, dar din păcate nu pare să funcționeze.

Ceea ce încerc să fac este ca metoda de extensie să inițializeze harta V ca un array și să adauge valoarea sau să adauge dacă este deja inițializată.

declare global {
interface Map<TKey, TValue[]>
 {
    SetInArray(key:TKey, value:TValue):boolean;
 }
}


Map.prototype.SetInArray = function<TKey, TValue>(key:TKey, value:TValue):boolean {

let isNew:boolean = false;

if(this.has(key) == false){

    this.set(key, []);

    isNew = true;

}

let items:TValue[] = this.get(key);

items.push(value);

return isNew;
};


export {};

Vă rog să mă sfătuiți

Mulțumesc

Răspuns #1

Există câteva probleme (pe lângă faptul că înțelepciunea populară spune că nu ar trebui să extinzi prototipurile obiectelor native). Prima este că atunci când declari o interfață sau un tip generic, parametrul său de tip trebuie să fie un nume simplu, nu o funcție a unui alt tip. Deci nu poți avea

interface Foo<T, U[]> {} // error

Dacă necesități ca al doilea parametru de tip să fie un tablou, ar trebui să folosiți o constrângere generică:

interface Foo<T, U extends any[]> {} // okay

Următoarea problemă este că nu poți combina declarațiile de tipuri generice dacă modifici parametrii de tip sau constrângerile acestora într-un fel. Map<K, V> este deja definit în biblioteca standard, așa că nu îl poți schimba în Map<TKey, TValue[]> sau Map<TKey, TValue extends any[]> sau chiar Map<K, V extends any[]>. Trebuie doar să îl lași ca Map<K, V>.

Noua metodă pe care o adaugi necesită ca V să fie de tip array pentru a avea sens. Și nu poți restricționa V în declarația ta. Ești blocat? Nu! Din fericire, poți folosi o caracteristică TypeScript numită parametru this. Ideea este că poți adăuga un parametru numit this la începutul listei tale de parametri într-o funcție sau metodă. Când apelezi metoda, nu folosești parametrul this, dar TypeScript va impune ca metoda să fie apelată doar pe un obiect al cărui context this corespunde.

Așa:

// no error
declare global {
  interface Map<K, V> {
    SetInArray<V>(this: Map<K, V[]>, key: K, value: V): boolean;
  }
}

Map.prototype.SetInArray = function <K, V>(this: Map<K, V[]>, key: K, value: V): boolean {
  let isNew: boolean = false;
  if (this.has(key) == false) {
    this.set(key, []);
    isNew = true;
  }
  let items: V[] = this.get(key)!;
  items.push(value);
  return isNew;
};

Hai să încercăm:

const numberArrayMap = new Map<string, number[]>();
numberArrayMap.set("a", [1, 2, 3])
numberArrayMap.SetInArray("a", 4); // okay

const numberMap = new Map<string, number>();
numberMap.set("a", 4)
numberMap.SetInArray("a", 4); // error
// The 'this' context of type 'Map<string, number>' is not assignable 
// to method's 'this' of type 'Map<string, number[]>'. 
// Type 'number' is not assignable to type 'number[]'.

Acesta este exact comportamentul pe care îl dorești, cred. Observă cum numberArrayMap îți permite să apelezi SetInArray(), dar numberMap nu, iar eroarea îți spune că, deși Map are într-adevăr o metodă SetInArray, pur și simplu nu o poți apela din cauza unui context this incompatibil.

Bine, sper că asta ajută. Mult noroc!