Reduce — JavaScript

Lucas Coelho da Costa
4 min readOct 16, 2021

--

Eu demorei bastante a entender como o reduce funciona, mais do que eu “me orgulho”. Mas tá tudo bem. Não é algo que você necessariamente precisa usar todos os dias. Embora algo aqui e acolá poderia ser simplificado com um uso do reduce, nos momentos em que nós não sabíamos disso provavelmente nos viramos de outra forma.

Foto por Brenda Godinez no Unsplash

Dependendo da sua bolha e redes sociais, provavelmente você já viu aquele clássico exemplo mostrando diversos emojis de fruta em um array e como o reduce de fato “reduz” o array a uma salada de frutas. É um exemplo bonitinho de ser ilustrado, mas não dá nenhuma ajuda real.

De forma curta, isso é uma implementação do reduce:

exemplo simples de reducer que soma os valores de um array

E pode ser outro exemplo dos que encontramos facilmente em todo lugar. De todo modo, vamos incrementando aos poucos!

O reduce, tal qual .map(), .filter(), .forEach() e outros, realizam uma iteração em um Array, com a diferença de que o reduce espera que a partir do seu array você gere um único valor.

No trecho de código acima, o que chamamos de acc ou accumulator (acumulador) é o valor anterior da iteração, que vai começar como o valor inicial do seu array[¹] (nesse exemplo, o 1). Enquanto isso, current é o valor atual da iteração, que nesse início vai ser o segundo elemento (o 2)[²].

Podemos pensar então na execução desses passos da seguinte forma:

exemplo do que está acontecendo no código

[¹]: Caso você passe um valor inicial no seu reducer, esse comportamento é diferente, isso será mostrado mais abaixo.

[²]: O seu array pode ter apenas um elemento, mas nesse caso, indiferentemente do retorno do seu reducer (a função que passamos dentro do reduce), o valor vai ser o único contido dentro do array original. Dado o seguinte trecho de código:

exemplo simples de reducer que irá retornar o único valor do array

Mesmo que o retorno seja duas vezes a soma do acumulador e o valor atual, o retorno será 1. Isso porque ele nem estará sendo performado.

O .reduce() também aceita como segundo parâmetro um valor inicial. Nesse caso, invés do primeiro elemento ele irá começar desse seu valor inicial:

exemplo simples de reducer somando valores de um array, com valor inicial em 10

Caso você quisesse obter o mesmo resultado acima sem usar um reduce por exemplo, você teria algo assim:

exemplo paralelo, usando forEach

Reducer callback

Eu mencionei um pouco acima sobre a função ‘reducer’, que é a que você passa ao chamar .reduce(), mas ela vai além dos exemplos usados. Essa callback pode receber 4 parâmetros:

  • (1) Valor anterior, (2) valor atual, (3) índice atual e (4) array.

Os dois primeiros já foram explicados. Os dois últimos são ainda mais simples: o índice atual se refere (claro) ao índice do valor atual, isso significa que ele irá sempre começar em 1, que é o segundo elemento[³] do seu array. Já o array, é simplesmente o array que “está sendo reduzido”. No exemplo inicial: [1, 2, 3, 4, 5, 6].

[³]: Caso você não tenha passado um segundo parâmetro definindo o valor inicial.

Mas qual a relevância desses outros parâmetros?

Vamos supôr que você tenha um reducer complexo. Por organização, é bom mantê-lo separado para facilitar a manutenção. Neste caso, você precisa de uma forma de acessar o array recebido:

exemplo simples de reducer que une um array de strings

Este exemplo é puramente didático, não deve ser tratado como a melhor forma de resolver esse tipo de problema.

Mas antes: o que esse trecho nos resolve?

  • Preciso que dado um input como [‘nome1’, ‘nome2’, ‘nome3’], eu consiga uma string como “nome 1, nome2 e nome3”.

Durante a iteração, foram usados os parâmetros currentIndex e array para descobrir se o elemento atual é o último, para assim definir se o prefixo será uma separação por vírgula ou por ‘e ‘. É importante que nós tenhamos acesso a essas informações mesmo em exemplos como esse, onde o reducer está fora do escopo da função e não sabe o que é o array original. É claro que nesse caso seria mais simples só manter o reducer no escopo da função, mas como mencionei, esse exemplo foi feito para fins didáticos.

Caso de uso real

Recentemente, fiz a adição de uma funcionalidade que permite a criação de temas customizados para o nosso Design System no Gympass, o Yoga. Dentre as adições, houveram mudanças em código existente e uma das sugestões no review do meu pull request era a reescrita de um forEach para usar um reducer. Se você quiser ver o código, é só vir aqui: diff.

Pra evitar trazer toda a complexidade e contexto envolvidos, vou fazer um exemplo diferente aqui:

exemplo mínimo de como usamos o reduce em um projeto real

Resumindo: dado o array que temos no código, o retorno vai ser um objeto nesse formato:

(resumido com { … }, a ideia é a mesma)

Antes isso era feito com um forEach, que tal como o reduce itera um array, mas não é feito para manipulações exatamente. O código original, que criava um objeto vazio que sofria mutações durante a iteração como:

components[field] = { ...components[index].styles }

Já com o reduce, temos apenas um novo objeto criado a partir de um array que não realizou mutações a nenhuma variável.

Há “controvérsias” e nem todo mundo acha que usar o reduce é um ganho. Mas ao menos, agora você tem essa ferramenta no seu canivete suíco.

--

--