Wykorzystanie funkcji wyższego rzędu, takich jak map()
, czy filter()
, jest w JavaScript powszechne, jednak podczas refactoringów niezwykle rzadko zdarza mi się natknąć na funkcję reduce()
. Prawdopodobnie natłok pracy i napięte terminy źle wpływają na kreatywność i popychają programistów utartymi, dobrze znanymi ścieżkami. Można się oczywiście bez reduce()
obejść i zrobić wszystko proceduralnie za pomocą pętli. Lepiej jednak odchudzić kod o mechanizm zarządzania pętlą, czym zajmie się funkcja, a samemu skupić się na rozwiązaniu problemu.
Dla przypomnienia callback funkcji .reduce()
może przyjąć poniższe argumenty:
• accumulator (acc) – wartość zwrócona w ostatnim wywołaniu callback;
• currentValue (cv) – element tablicy przetwarzany w danym wywołaniu;
• index (i) – indeks przetwarzanego elementu;
• array (arr) – tablica na której wywoływana jest funkcja reduce.
Składnia funkcji wygląda następująco:
sourceArray.reduce(callback(acc, cv, i, arr), initialValue)
Opcjonalny parameter initialValue pozwala podać argument do pierwszego wywołania funkcji calbback w miejsce wartości acummulator.
Najprostszy, często podawany, ale rzadko użyteczny w praktyce, przykład wykorzystania reduce, to zsumowanie wszystkich elementów tablicy:
const arr = [1, 2, 3, 4, 5] const sum = arr.reduce((acc, cv) => acc + cv)
Wygląda bardzo czytelnie i zwięźle. Jednak reduce()
pozwala na znacznie więcej. Załóżmy że mamy w tablicy dane wybranych produktów naszego sklepu i chcemy uzyskać informacje na temat ich liczby w podziale na producentów. Przykładowa tablica wygląda tak:
const products = [ { id: 1, name: "DDR4 32GB", category: {id: 4, name: "Pamięci RAM"}, company: {id: 8, name: "Kingston"}, price: 1291.00, availability: true }, { id: 2, name: "DDR5 32GB", category: {id: 4, name: "Pamięci RAM"}, company: {id: 17, name: "Adata"}, price: 1493.00, availability: false }, { id: 3, name: "DDR5 16GB", category: {id: 4, name: "Pamięci RAM"}, company: {id: 8, name: "Kingston"}, price: 753.00, availability: true }, { id: 4, name: "DDR4 16GB", category: {id: 4, name: "Pamięci RAM"}, company: {id: 8, name: "Kingston"}, price: 511.00, availability: true }, { id: 5, name: "DDR2 8GB", category: {id: 4, name: "Pamięci RAM"}, company: {id: 9, name: "GOODRAM"}, price: 134.00, availability: false } ]
Wykorzystując reduce()
możemy napisać to równie czytelnie, jak we wcześniejszym przykładzie sumowania. Do funkcji callback, którą nazwijmy companyReducer, jako currentValue przekażemy zdestrukturyzowaną wartość interesującej nas właściwości company.name.
const companyReducer = (acc, { company: { name } }) => {...}
Przejdźmy teraz do ciała funkcji. Chcemy zwrócić nowy obiekt zawierający właściwość company.name, która będzie pobierana z kolejnego elementu w każdej iteracji. Ponieważ chcemy dostać informację o liczbie wystąpień danej firmy, musimy dodać warunek, który w przypadku ponownego wystąpienia danej wartości zwiększy licznik, natomiast dla nowych wartości stworzy ją z licznikiem równym jeden. Na koniec musimy jeszcze pamiętać o przekazaniu przez warunkiem zdestrukturyzowanej wartości z poprzedniego wywołania.
const companyReducer = (acc, { company: { name } }) => ({ ...acc, [name]: acc[name] ? acc[name] + 1 : 1 })
Ponieważ chcemy zwrócić obiekt różniący się strukturą od danych wejściowych, nie możemy przyjąć jako stan początkowy pierwszego elementu z naszej tablicy. Dlatego do funkcji reduce, poza funkcją callback podamy opcjonalny parametr, jakim jest pusty obiekt, który zostanie użyty jako stan początkowy.
const company = products.reduce(companyReducer, {})
W wyniku wywołania tak zdefiniowanej funkcji dostaniemy poniższy obiekt, co było naszym celem.
{ "Kingston": 3, "Adata": 1, "GOODRAM": 1 }
Funkcja reduce()
w swojej konstrukcji jest bardziej skomplikowana od map()
, czy filter()
, jednak dzięki temu, że przechowuje zakumulowany stan z poprzedniej operacji, pozwala na znacznie więcej.
Kompletny kod dostępny jest na CodePen.