Funkcja reduce w JavaScript

Funkcja reduce w JavaScript

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.