Reduce — JavaScript
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.
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:
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:
[¹]: 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:
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:
Caso você quisesse obter o mesmo resultado acima sem usar um reduce por exemplo, você teria algo assim:
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:
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:
Resumindo: dado o array que temos no código, o retorno vai ser um objeto nesse formato:
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.