Iteracja po mapie Typescript

136

Próbuję iterować po mapie maszynopisu, ale ciągle pojawiają się błędy i nie mogłem jeszcze znaleźć rozwiązania dla tak trywialnego problemu.

Mój kod to:

myMap : Map<string, boolean>;
for(let key of myMap.keys()) {
   console.log(key);
}

I otrzymuję błąd:

Typ „IterableIteratorShim <[string, boolean]>” nie jest tablicą ani ciągiem.

Pełny ślad stosu:

 Error: Typescript found the following errors:
  /home/project/tmp/broccoli_type_script_compiler-input_base_path-q4GtzHgb.tmp/0/src/app/project/project-data.service.ts (21, 20): Type 'IterableIteratorShim<[string, boolean]>' is not an array type or a string type.
    at BroccoliTypeScriptCompiler._doIncrementalBuild (/home/project/node_modules/angular-cli/lib/broccoli/broccoli-typescript.js:115:19)
    at BroccoliTypeScriptCompiler.build (/home/project/node_modules/angular-cli/lib/broccoli/broccoli-typescript.js:43:10)
    at /home/project/node_modules/broccoli-caching-writer/index.js:152:21
    at lib$rsvp$$internal$$tryCatch (/home/project/node_modules/rsvp/dist/rsvp.js:1036:16)
    at lib$rsvp$$internal$$invokeCallback (/home/project/node_modules/rsvp/dist/rsvp.js:1048:17)
    at lib$rsvp$$internal$$publish (/home/project/node_modules/rsvp/dist/rsvp.js:1019:11)
    at lib$rsvp$asap$$flush (/home/project/node_modules/rsvp/dist/rsvp.js:1198:9)
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickCallback (internal/process/next_tick.js:98:9)

Używam angular-cli beta5 i maszynopisu 1.8.10, a moim celem jest es5. Czy ktoś miał ten problem?

mwe
źródło
4
Zobacz tę odpowiedź z github github.com/Microsoft/TypeScript/issues/…
yurzui

Odpowiedzi:

214

Możesz Map.prototype.forEach((value, key, map) => void, thisArg?) : voidzamiast tego użyć

Użyj tego w ten sposób:

myMap.forEach((value: boolean, key: string) => {
    console.log(key, value);
});
rinukkusu
źródło
16
Właśnie wpadłem na to. Nie wygląda na to, że TypeScript przestrzega specyfikacji iteracji mapy, przynajmniej zgodnie z MDN, który określa pętlę for-of. .forEachjest nieoptymalny, ponieważ nie możesz go złamać, AFAIK
Samjones
14
nie wiem, dlaczego jego wartość to klucz. Wydaje się odwrócony.
Paul Rooney,
@PaulRooney to chyba tak, aby zharmonizować Array.prototype.map.
Ahmed Fasih
1
Jak zatrzymać ten proces iteracji, jeśli znaleźliśmy to, czego potrzebujemy?
JavaRunner
37

es6

for (let [key, value] of map) {
    console.log(key, value);
}

es5

for (let entry of Array.from(map.entries())) {
    let key = entry[0];
    let value = entry[1];
}
Oded Breiner
źródło
1
Podana powyżej wersja es6 nie jest kompilowana w trybie ścisłym.
Stephane
Uprzejmie panu dziękuję.
Kitanga Nday
36

Po prostu użyj Array.from()metody, aby przekonwertować go na Array:

myMap : Map<string, boolean>;
for(let key of Array.from( myMap.keys()) ) {
   console.log(key);
}
6qat
źródło
5
Konwertowanie map jest operacją wymagającą dużej wydajności i nie jest właściwym sposobem rozwiązania problemu, który zasadniczo polega tylko na tym, że kompilator ukrywa podstawowe części języka. Po prostu <any>wyślij mapę, aby wykonać iterację za pomocą for-of.
Kilves,
1
Uwaga: uważam, że sugestia @Kilves powyżej, aby przesłać mapę na „dowolny”, była eleganckim obejściem. Kiedy to zrobiłem, kod skompilował się i działał bez reklamacji, ale Mapa nie była w rzeczywistości iterowana - zawartość pętli nigdy nie została wykonana. Array.from()Strategia proponowana tu nie działa dla mnie.
mactyr
Ja też tego próbowałem, ale to też mi się nie udało, co jest jeszcze głupsze, biorąc pod uwagę, że jest częścią ES6 i powinno „po prostu działać” na większości przeglądarek. Ale wydaje mi się, że nasi kanciści władcy używają magicznych mumbo jumbo w zone.js, aby to nie zadziałało, ponieważ nienawidzą ES6. Westchnienie.
Kilves,
28

Korzystanie z funkcji Array.from , Array.prototype.forEach () i strzałkowych :

Powtarzaj po klawiszach :

Array.from(myMap.keys()).forEach(key => console.log(key));

Iteruj po wartościach :

Array.from(myMap.values()).forEach(value => console.log(value));

Powtarzaj wpisy :

Array.from(myMap.entries()).forEach(entry => console.log('Key: ' + entry[0] + ' Value: ' + entry[1]));
Tobold
źródło
2
Nie wiem dlaczego, mam mapę taką jak Map <String, CustomeClass>. żadna z powyższych metod nie działała poza Array.from (myMap.values ​​()). forEach (value => console.log (value)) ;.
Rajashree Gr
27

To zadziałało dla mnie. Wersja TypeScript: 2.8.3

for (const [key, value] of Object.entries(myMap)) { 
    console.log(key, value);
}
Debashish Sen
źródło
7
Mam to do pracy, zmieniając Object.entries (myMap) na po prostu myMap.entries (). Podoba mi się ta odpowiedź, ponieważ pozwala uniknąć pułapek obsługi błędów wywołań .forEach.
spotkanie
2
Warto zauważyć, że jeśli targetw tsconfigjest es5to zgłasza błąd, ale es6działa poprawnie. Możesz też po prostu zrobić for (const [key, value] of myMap)celowaniees6
Benjamin Vogler
16

Zgodnie z uwagami do wydania TypeScript 2.3 „Nowy --downlevelIteration :

for..of statements, Niszczenie tablic i rozkładanie elementów w wyrażeniach Array, Call i New obsługują Symbol.iterator w ES5 / E3, jeśli są dostępne podczas używania --downlevelIteration

Nie jest to domyślnie włączone! Dodaj "downlevelIteration": truedo swojej flagi tsconfig.jsonlub przekaż --downlevelIterationtsc, aby uzyskać pełną obsługę iteratora.

Z tego miejsca, możesz pisać for (let keyval of myMap) {...}i keyval„s typ zostanie automatycznie wnioskować.


Dlaczego jest to domyślnie wyłączone? Według autora TypeScript @aluanhaddad ,

Jest opcjonalny, ponieważ ma bardzo duży wpływ na rozmiar generowanego kodu i potencjalnie na wydajność dla wszystkich zastosowań iterowalnych (w tym tablic).

Jeśli możesz kierować reklamy na ES2015 ( "target": "es2015"w wersji tsconfig.jsonlub tsc --target ES2015) lub później, włączenie downlevelIterationjest oczywiste, ale jeśli celujesz w ES5 / ES3, możesz wykonać test porównawczy, aby upewnić się, że obsługa iteratorów nie wpływa na wydajność (jeśli tak, możesz być lepszy wyłączyć z Array.fromkonwersją lub w forEachinny sposób).

Ahmed Fasih
źródło
czy włączenie tej opcji podczas korzystania z Angular jest w porządku czy niebezpieczne?
Simon_Weaver
9

To zadziałało dla mnie.

Object.keys(myMap).map( key => {
    console.log("key: " + key);
    console.log("value: " + myMap[key]);
});
Jason Slobotski
źródło
2
Dzięki temu klucze zawsze będą jednak ciągami
Ixx
8

Używam najnowszego TS i węzła (odpowiednio v2.6 i v8.9) i mogę:

let myMap = new Map<string, boolean>();
myMap.set("a", true);
for (let [k, v] of myMap) {
    console.log(k + "=" + v);
}
lazieburd
źródło
Czy możesz potwierdzić, że najpierw musiałeś ustawić "downlevelIteration": truew swoim tsconfig.json?
Ahmed Fasih
3
Nie mam downlevelIteratonwyznaczonego, mój cel jest es2017jednak.
lazieburd
Nie mogę tego zrobić dla elementu Map <string, CustomClass []>? kompilator mówi, że nie jest to tablica typu ani typu string.
Rajashree Gr
to nie działa dla mnie na 2.8 - dostajęType 'Map<K, V>' is not an array type or a string type.
Simon_Weaver
3

Możesz również zastosować metodę mapowania tablic do iterowalnej metody Map.entries ():

[...myMap.entries()].map(
     ([key, value]: [string, number]) => console.log(key, value)
);

Ponadto, jak zauważono w innych odpowiedziach, może być konieczne włączenie iteracji niższego poziomu w tsconfig.json (w opcjach kompilatora):

  "downlevelIteration": true,
cham
źródło
Ta funkcja została wprowadzona w języku TypeScript 2.3. Problem wystąpił z TypeScript 1.8.10
mwe
Jeśli nie możesz użyć "downlevelIteration", możesz użyć:const projected = Array.from(myMap).map(...);
Efrain
1

Wystarczy proste wyjaśnienie, jak używać go w dokumencie HTML.

Jeśli masz Mapę typów (klucz, tablica), to inicjalizujesz tablicę w ten sposób:

public cityShop: Map<string, Shop[]> = new Map();

Aby wykonać iterację, tworzysz tablicę z wartości kluczowych.

Po prostu użyj go jako tablicy, jak w:

keys = Array.from(this.cityShop.keys());

Następnie w HTML możesz użyć:

*ngFor="let key of keys"

Wewnątrz tej pętli otrzymujesz wartość tablicy za pomocą:

this.cityShop.get(key)

Gotowe!

Sophie C Musical
źródło
1

W przypadku Typescript 3.5 i Angular 8 LTS wymagane było rzutowanie typu w następujący sposób:

for (let [k, v] of Object.entries(someMap)) {
    console.log(k, v)
}
Constantin Konstantinidis
źródło
-1

Jeśli naprawdę nie lubisz funkcji zagnieżdżonych, możesz także iterować po klawiszach:

myMap : Map<string, boolean>;
for(let key of myMap) {
   if (myMap.hasOwnProperty(key)) {
       console.log(JSON.stringify({key: key, value: myMap[key]}));
   }
}

Uwaga, musisz odfiltrować iteracje niebędące kluczami za pomocą hasOwnProperty, jeśli tego nie zrobisz, otrzymasz ostrzeżenie lub błąd.

peterh - Przywróć Monikę
źródło
jak iterować mapę w html? wydaje się, że w ogóle nie działa. <div ng-repeat = "(klucz, wartość) w model.myMap"> {{klucz}}. </div>
Shinya Koizumi
@ powerfade917 To nie działa, działa tylko dla tablic, ponieważ angular to kupa śmieci. Ale zadaj to jako nowe pytanie, a dowiesz się, że angular nie jest kupą śmieci, ale musisz go przekonwertować na tablicę. Zwróć uwagę, że nie jesteś na szczycie programowania, ponieważ pozornie nie jesteś w stanie odróżnić pisma kątowego od maszynopisu.
peterh - Przywróć Monikę