Menu

Pętle w TypeScript: for, forEach, for...of i for...in

Wszystkie sposoby iterowania w TypeScript i typy, które daje każdy z nich: klasyczna pętla for, for...of po tablicach, mapach i stringach, for...in i jego klucze typu string, forEach (bez break i await), typowane pętle po kluczach obiektu oraz while.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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. break w callbacku to błąd kompilacji TS1107, Jump target cannot cross function boundary. return kończy tylko bieżące wywołanie, więc działa jak continue.
  • Czekać na pracę asynchroniczną. forEach ignoruje promise zwracane przez callback async.

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ć

CelPętla
Każdy element tablicyfor (const x of arr)
Element i indeksfor (const [i, x] of arr.entries()) albo arr.forEach((x, i) => ...)
Wcześniejsze zakończeniefor...of z break albo find, some, every
Czekanie na każdy krokfor...of z await w środku
Budowanie nowej tablicymap, filter (nie pętla)
Liczenie albo krok co nfor (let i = 0; ...; i += n)
Klucze i wartości obiektufor (const [k, v] of Object.entries(obj))
Wpisy Mapfor (const [k, v] of map)
Do spełnienia warunkuwhile

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) => ...)).

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ