Array.reduce() - Entenda como funciona o método reduce e aprenda como usá-lo para resolver problemas comuns do seu código JavaScript!
Assim como suas primas Array.filter() e Array.map(), Array.reduce() é uma função iteradora que se popularizou no ECMAScript 6.
Função iteradora? Isso mesmo! Uma função iteradora é aquela que, quando chamada, percorre todos os elementos de uma lista, executa uma regra para cada um desses elementos e, ao final, retorna alguma coisa.
Diferente de outros métodos que nos permitem iterar sobre um array, reduce é uma função bem mais flexível que suas semelhantes. Isto porque ela pode retornar praticamente o que quisermos: um objeto, um novo array, um number, uma string...
Sua principal característica é reduzir (daí o nome) uma coleção a um único elemento.
Array.reduce() e seus parâmetros
Antes de explorar o verdadeiro poder desta função, vamos dar uma olhadinha na sua sintaxe:
array.reduce(callback(acumulador, elementoAtual, indice, arrayOriginal), valorInicial);
Destrinchando:
- Primeiro, chamamos a função reduce sobre um array com o ‘.reduce’, passando para ela seu primeiro argumento: uma função callback.
- Esta função callback, por sua vez, espera receber quatro argumentos;
- O Acumulador, que será a variável que armazenará o resultado de todas as nossas iterações e será, ao final, retornada;
- O elementoAtual que está sendo percorrido pela função na iteração;
- O índice do elementoAtual (argumento opcional) e
- O arrayOriginal sobre o qual o método reduce está iterando (argumento opcional).
- Terminamos a declaração da função callback e passamos para o reduce o valorInicial, seu segundo e último argumento.
Array.reduce() na prática!
Agora que conhecemos a assinatura do método, chegou a hora de vermos como ele funciona na prática! Para isso, começaremos com um exemplo simples.
Somando todos os valores em um array
Abaixo temos um array de numbers cujos elementos desejamos somar:
const numbers = [10, 1, 2, 5];
Somaremos seus itens imprimindo no console o valor de cada variável durante cada uma das iterações:
numbers.reduce((acumulador, elementoAtual, indice, arrayOriginal) => { console.log(`índice: ${indice}`); console.log(`acumulador: ${acumulador}`); console.log(`elemento atual: ${elementoAtual}`); console.log(`array original: ${arrayOriginal}`); return acumulador + elementoAtual; }, 0);
Ao rodar o código acima, o console nos retorna:
índice: 0 acumulador: 0 elemento atual: 10 array original: [10, 1, 2, 5] índice: 1 acumulador: 10 elemento atual: 1 array original: [10, 1, 2, 5] índice: 2 acumulador: 11 elemento atual: 2 array original: [10, 1, 2, 5] índice: 3 acumulador: 13 elemento atual: 5 array original: [10, 1, 2, 5] 18
Podemos perceber que a variável acumulador começa com o valor 0 e, a cada iteração, é acrescida pelo valor do elemento atual, conforme a regra que definimos em “return acumulador + elementoAtual”.
Após a última iteração, a função retorna o valor “acumulado” na variável acumulador, ou seja, 18.
O que aconteceria, no entanto, se não passássemos o valor inicial como segundo argumento?
Vejamos o que o console nos retornará:
índice: 1 acumulador: 10 elemento atual: 1 array original: [10, 1, 2 ,5] índice: 2 acumulador: 11 elemento atual: 2 array original: [10, 1, 2, 5] índice: 3 acumulador: 13 elemento atual: 5 array original: [10, 1, 2, 5] 18
Como podemos constatar, quando o valor inicial não é fornecido, reduce executará a função callback começando pelo item com índice 1, ignorando, assim, o primeiro elemento do array. Por este motivo, a variável acumulador assumirá, na primeira iteração, o valor do item com índice 0. Desse modo, o resultado retornado ao final permanecerá o mesmo que o do exemplo anterior.
Nada muito mirabolante nem surpreendente até aqui, não é? Certamente poderíamos fazer o mesmo com nosso velho forEach, da seguinte forma:
let soma = 0; numbers.forEach(number => soma += number); return soma;
Array.reduce() e suas maravilhas!
Veremos agora alguns exemplos de como utilizar o Array.reduce() para resolver problemas cotidianos do nosso código!
Aplainando uma matriz
Lembra de quando falamos que reduce é um método bem mais flexível que outros iteradores de array e que, por isso, pode retornar o que quisermos? É justamente por esse motivo que ele pode fazer muito mais que apenas somar, multiplicar, subtrair ou dividir uma coleção de elementos ordenados.
Imaginemos, agora, um vetor bidimensional (um array de arrays)!
const arrayDeArrays = [[“a”, 1], [“b”, 3], [“c”, 20]];
Desejamos transformá-lo num vetor unidimensional. Com o método reduce, podemos fazer isso de maneira simples!
arrayDeArrays.reduce((acumulador, elementoAtual) => acumulador.concat(elementoAtual), [“d”, 3]);
Acima, declaramos que, a cada iteração, a função callback deve executar o método concat() sobre o atual estado de acumulador, recebendo como argumento o valor de elementoAtual. Definimos, ainda, o valor inicial de acumulador como sendo um array contendo dois elementos: “d” e 3.
Não sabe o que é o método concat? É simples! Ele nada mais faz que retornar um novo array, concatenando todos os valores passados como argumentos com os valores do array sobre o qual ele é chamado.
Vamos simular a mudança de estado da função a cada iteração para vermos o que está acontecendo:
índice: 0 acumulador: [“d”, 3] elemento atual: [“a”, 1] array original: [ [“a”, 1], [“b”, 3], [“c”, 20] ] índice: 1 acumulador: [“d”, 3, “a”, 1] elemento atual: [“b”, 3] array original: [ [“a”, 1], [“b”, 3], [“c”, 20] ] índice: 2 acumulador: [“d”, 3, “a”, 1, “b”, 3] elemento atual: [“c”, 20] array original: [ [“a”, 1], [“b”, 3], [“c”, 20] ]
Fim do loop!
Retorno final: [“d”, 3, “a”, 1, “b”, 3, “c”, 20]
Legal, né? Vamos agora para um exemplo um pouco mais complexo.
Agrupando objetos por suas propriedades
Imaginemos uma empresa com vários funcionários. Vamos supor, então, que, nessa empresa, há gerentes, secretários, advogados e contadores. Veja abaixo como poderíamos representar este cenário com um array de objetos:
const funcionarios = [ { nome: “Roberto”, idade: 52, função: “gerência” }, { nome: “Pedro”, idade: 25, função: “contabilidade” }, { nome: “'Flávia”, idade: 38, função: “advocacia” }, { nome: “Sandra”, idade: 27, função: “contabilidade” }, { nome: “Rafael”, idade: 21, função: “secretaria” } ];
Cada um dos objetos no array representa um funcionário com suas respectivas propriedades: nome, idade e função.
Vamos imaginar que, por algum motivo, precisemos organizá-los de acordo com a função exercida. Vamos fazer isso criando uma função que usará nosso reduce!
const agruparPor = (array, propriedade) => { return array.reduce((objeto, elementoAtual) => { const grupo = elementoAtual[propriedade]; if (!objeto.hasOwnProperty(grupo)) { objeto[grupo] = []; } objeto[grupo].push(elementoAtual); return objeto; }, {}) }
O que está acontecendo aqui?
Primeiro, criamos uma função agruparPor com dois parâmetros: o array sobre o qual desejamos iterar e a propriedade que servirá como critério para agruparmos nossos elementos.
const agruparPor = (array, propriedade) => { ... }
Definimos o retorno desta função como sendo o retorno da chamada do método reduce sobre a variável array. Perceba que reduce, aqui, recebe um objeto vazio como valor inicial.
return array.reduce((objeto, elementoAtual) => { ... }, {});
Como se trata de um array de objetos, armazenamos na variável grupo o valor que usaremos para agrupá-los.
const grupo = elementoAtual[propriedade];
Depois, checamos se a “variável acumuladora” objeto possui aquela propriedade. Caso não a possua, ordenamos que ela seja criada recebendo um array vazio como valor. Do contrário, se a função encontrar aquela propriedade no objeto, faremos um push do elementoAtual.
if (!objeto.hasOwnProperty(grupo)) { objeto[grupo] = []; } objeto[grupo].push(elementoAtual);
Por fim, retornamos a variável objeto
return objeto;
Quando chamamos a função agruparPor passando o array funcionários e a string ‘função’ como argumentos, obtemos como retorno um objeto contendo nossos funcionários agrupados pela atividade exercida na empresa:
console.log(agruparPor(funcionarios, “função”)); { gerência: [ { nome: 'Roberto', idade: 52, 'função': 'gerência' } ], contabilidade: [ { nome: 'Pedro', idade: 25, 'função': 'contabilidade' }, { nome: 'Sandra', idade: 27, 'função': 'contabilidade' } ], advocacia: [ { nome: 'Flávia', idade: 38, 'função': 'advocacia' } ], secretaria: [ { nome: 'Rafael', idade: 21, 'função': 'secretaria' } ] }
Gerando HTML dinamicamente
Ok, mas e se, em vez de apenas agruparmos nossos funcionários em um objeto, quiséssemos gerar uma lista em HTML com seus nomes e funções? Podemos fazer isso passando para nosso reduce o valor inicial de uma string vazia (“”) e trabalhando com seu parâmetro acumulador, que chamaremos de html. Vejamos:
const geraListaHTML = (array) => { return "<ul>" + array.reduce((html, elementoAtual) => { html += `<li>nome: ${elementoAtual[“nome”]}, função: ${elementoAtual[“função”]}</li>`; return html; }, “”) + "</ul>" } console.log(geraListaHTML(funcionarios));
E voilà, o resultado!
"<ul> <li>nome: Roberto, função: gerencia</li> <li>nome: Pedro, função: contabilidade</li> <li>nome: Flávia, função: advocacia</li> <li>nome: Sandra, função: contabilidade</li> <li>nome: Rafael, função: secretaria</li> </ul>"
Gostou?
Não é exagero dizer que Array.reduce() se tornou uma das minhas funções favoritas em JavaScript. Atualmente, o método é suportado pela grande maioria dos browsers e pode te poupar implementações mais complicadas e menos performáticas para realizar a mesma tarefa. Como toda ferramenta em programação, reduce é uma carta na manga que, se utilizada da maneira adequada, pode contribuir para melhorar a legibilidade, tamanho e performance do seu código!