TypeScript ma te same pętle co JavaScript: for, for...of, for...in, while, do...while i metodę tablic forEach. TypeScript dodaje do nich typy: zmienna pętli for...of ma typ elementów, a klucz w for...in zawsze jest typu string.
W większości pętli po tablicach warto sięgać po for...of: dobrze się czyta, obsługuje break, continue i await i nie wymaga adnotacji.
Klasyczna pętla for
for (init; condition; update) to pętla do liczenia, przechodzenia z krokiem większym niż jeden albo od końca. TypeScript wnioskuje typ licznika z jego wartości początkowej.
Deklaruj licznik przez let, a nie var. Przy let każda iteracja dostaje własne wiązanie, więc callback utworzony w pętli widzi wartość z tej iteracji.
for...of: wartości tablic, stringów i map
for...of działa ze wszystkim, co jest iterowalne, a zmienna pętli dostaje odpowiedni typ. Żeby mieć też indeks, iteruj po array.entries(), które zwraca krotki [index, value].
break wychodzi z pętli, a continue przeskakuje do następnego elementu, dokładnie jak w pętli for. Szczegóły działania (obiekty iterowalne, generatory) są na stronie JavaScriptu o for...of i for...in.
forEach: bez break i bez await
forEach wywołuje funkcję dla każdego elementu i zwraca undefined. Parametry callbacku są typowane na podstawie tablicy, więc adnotacje nie są potrzebne. Dwóch rzeczy nie potrafi:
- Zakończyć wcześniej.
breakw callbacku to błąd kompilacji TS1107,Jump target cannot cross function boundary.returnkończy tylko bieżące wywołanie, więc działa jakcontinue. - Czekać na pracę asynchroniczną.
forEachignoruje promise zwracane przez callbackasync.
Pułapkę z async łatwo przeoczyć, bo kod kompiluje się bez ostrzeżenia:
Callbacki forEach kończą się w kolejności timerów (10, 20, 30), gdy pętla już poszła dalej. Wersja z for...of wykonuje je po kolei. Do pracy równoległej, na którą i tak chcesz poczekać, użyj await Promise.all(items.map(async (item) => { ... })).
for...in: klucze są stringami
for...in przechodzi po nazwach wyliczalnych właściwości obiektu. Klucz zawsze ma typ string, nawet w tablicach, gdzie kluczami są indeksy w postaci stringów. Przez to niewygodnie się nim indeksuje otypowany obiekt:
index.ts(5,22): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ name: string; age: number; }'.
No index signature with a parameter of type 'string' was found on type '{ name: string; age: number; }'.
TypeScript celowo typuje klucz jako string: w czasie działania obiekt może mieć więcej właściwości, niż wymienia jego typ (typowanie strukturalne dopuszcza dodatkowe), a for...in przechodzi też po odziedziczonych wyliczalnych właściwościach. Nie może więc obiecać, że klucz to "name" | "age". Rozwiązania są w następnej sekcji. Na tablicach w ogóle unikaj for...in.
Iterowanie po kluczach i wartościach obiektu
Object.entries daje pary [key, value] i jest typowym sposobem na iterowanie po obiekcie. Object.keys zwraca string[] z tego samego powodu co for...in, więc gdy wiesz, że obiekt nie ma dodatkowych kluczy, użyj asercji typu klucza.
Dla danych, których klucze nie są znane z góry, otypuj obiekt jako Record<string, number> albo użyj Map; oba sposoby opisuje strona o słownikach.
while i do...while
while sprawdza warunek przed każdym przebiegiem; do...while wykonuje ciało raz przed sprawdzeniem. Pasują do pętli, które nie przechodzą po kolekcji, na przykład do czytania, dopóki warunek nie zostanie spełniony. Zawężanie działa w ich wnętrzu: po sprawdzeniu !== undefined w warunku zmienna jest zawężona w ciele pętli.
Etykiety, break i continue w zagnieżdżonych pętlach
Zwykłe break wychodzi tylko z najbardziej wewnętrznej pętli. Żeby wyjść z pętli zewnętrznej, nadaj jej etykietę i podaj ją po break:
Której pętli użyć
| Cel | Pętla |
|---|---|
| Każdy element tablicy | for (const x of arr) |
| Element i indeks | for (const [i, x] of arr.entries()) albo arr.forEach((x, i) => ...) |
| Wcześniejsze zakończenie | for...of z break albo find, some, every |
| Czekanie na każdy krok | for...of z await w środku |
| Budowanie nowej tablicy | map, filter (nie pętla) |
| Liczenie albo krok co n | for (let i = 0; ...; i += n) |
| Klucze i wartości obiektu | for (const [k, v] of Object.entries(obj)) |
| Wpisy Map | for (const [k, v] of map) |
| Do spełnienia warunku | while |
Najczęściej zadawane pytania
Jak napisać pętlę for w TypeScript?
Tak samo jak w JavaScripcie: for (let i = 0; i < 5; i++) { ... }; TypeScript wnioskuje i jako number. Do przejścia po elementach tablicy krótsze jest for (const item of items), a item dostaje typ elementów tablicy.
Jak przerwać forEach w TypeScript?
Nie da się: break w callbacku to błąd kompilacji TS1107 (Jump target cannot cross function boundary), a return kończy tylko bieżące wywołanie callbacku, jak continue. Użyj for...of z break albo some/find/every, które z założenia kończą wcześniej.
Czym różni się for...of od for...in w TypeScript?
for...of przechodzi po wartościach iterowalnego obiektu (elementach tablicy, znakach stringa, wpisach Map) z ich prawdziwymi typami. for...in przechodzi po kluczach wyliczalnych właściwości obiektu, zawsze typu string, łącznie z indeksami tablicy w postaci stringów. Do tablic używaj for...of, a do obiektów Object.entries.
Jak przejść po kluczach i wartościach obiektu w TypeScript?
Użyj for (const [key, value] of Object.entries(obj)). key to string, a value to unia typów właściwości. Jeśli potrzebujesz key typu keyof typeof obj, użyj asercji: (Object.keys(obj) as (keyof typeof obj)[]), bo TypeScript celowo typuje Object.keys jako string[].
Czy można użyć await wewnątrz forEach?
Kod się skompiluje, ale forEach nie czeka na promise zwracane przez callback, więc pętla kończy się przed zakończeniem pracy. Do pracy sekwencyjnej użyj for...of z await w środku, a do równoległej await Promise.all(items.map(async (x) => ...)).