Reduce em inglês é reduzir.
O motivo disso é que a principal funcionalidade do reduce é reduzir uma array a apenas um valor. Veja um exemplo onde o reduce soma todos os números de uma array e retorna um número:
const numbers = [ 76, 23, 89, 54 ];
const result = numbers.reduce(function(acumulado, atual) {
return acumulado + atual;
});
console.log(result); // 242
O que está aconteceu aqui:
Na primeira iteração,
acumulado
é o primeiro da array, eatual
o segundoNas próximas,
acumulado
é o retorno das iterações anteriores, e oatual
é o próximo item da arrayApesar de a array ter 4 itens, houve apenas 3 iterações (vou mostrar como corrigir isso)
Iteração | acumulado | atual | Retorno (a soma dos números) |
---|---|---|---|
1 | 76 | 23 | 99 |
2 | 99 | 89 | 188 |
3 | 188 | 54 | 242 |
O valor que o reduce retorna não precisa ser apenas uma string ou número, pode ser um objeto e inclusive outra array.
Imagino que por esse motivo esse seja o método de array mais complicado de entender, não vou mentir. Mas estou aqui para tentar explicar tudo da melhor forma. Ao longo do post vou dar vários exemplos de uso do reduce, do mais fácil para o menos fácil, para ajudar na explicação.
O parâmetro do reduce é uma função callback. Porém existe um segundo parâmetro opcional (mas altamente recomendado) que muda um pouco a lógica do jogo.
O valor inicial do reduce
Você pode definir um ponto de partida para o reduce.
O segundo parâmetro, que é opcional e não usei antes, é o valor inicial. Vou recriar o primeiro exemplo com um valor inicial de 8:
const numbers = [ 76, 23, 89, 54 ];
function callback(acumulado, atual) {
return acumulado + atual;
}
const initialValue = 8;
const result = numbers.reduce(callback, initialValue);
console.log(result); // 250
Coloquei cada parâmetro em uma variável para facilitar a leitura. Como o reduce inicia em 8, o resultado que antes era 242, agora é 250.
Mas agora, o valor das variáveis acumulado
e atual
muda durante as iterações:
Iteração | acumulado | atual | Retorno (a soma dos números) |
---|---|---|---|
1 | 8 | 76 | 84 |
2 | 84 | 23 | 107 |
3 | 107 | 89 | 196 |
4 | 196 | 54 | 250 |
Agora, a quantidade de iterações corresponde ao número de itens da array, 4. Então por segurança, sempre defina o valor inicial
Na primeira iteração,
acumulado
é o valor que defini como inicial (8); eatual
representa o primeiro item da array, 76Nas próximas,
acumulado
é o resultado das iterações anteriores, e oatual
é um item da array
Colocar um valor inicial é como colocar um item no começo da array antes de começar o reduce. Ele facilita o entendimento, porque na primeira iteração não existe resultado anterior.
No código abaixo, eu adicionei o 8 no começo da array e removi ele do valor inicial do Reduce. O resultado é o mesmo, mas não recomendo fazer isso. O valor inicial esclarece melhor o que acontece no código.
Faça por sua conta e risco:
// Adicionei o 8 no começo da array
const numbers = [ 8, 76, 23, 89, 54 ];
function callback(acumulado, atual) {
return acumulado + atual;
}
// Sem initialValue
const result = numbers.reduce(callback);
console.log(result); // 250
Aos poucos, fica claro que o valor de retorno da função callback é muito importante. Vou dar um foco nela agora.
O retorno da função callback do reduce
Você pode retornar o que quiser.
O valor que você retornar em cada iteração será o valor do acumulado
na próxima. Esse é uma das grandes vantagens do reduce, você pode fazer operações com mais de um item da array.
Existe apenas uma exceção a isso, que é a última iteração. O retorno dela é o resultado final do método reduce:
Veja isso com um exemplo. Vou usar a array abaixo para fazer o reduce:
const products = [
{ id: '1a2b3c4d', name: 'Camiseta Básica', color: 'Branca', size: 'M', price: 200 },
{ id: '5e6f7g8h', name: 'Calça Jeans', color: 'Azul', size: 'L', price: 500 },
{ id: '9i0j1k2l', name: 'Bolsa de Couro', color: 'Marrom', size: 'Único', price: 800 },
{ id: '3m4n5o6p', name: 'Tênis Esportivo', color: 'Preto', size: '42', price: 300 },
{ id: '7q8r9s0t', name: 'Vestido Floral', color: 'Rosa', size: 'S', price: 350 }
];
Essa array representa os produtos de um carrinho de compras. Como faço para chegar no valor total dos produtos ao final do carrinho?
Para começar, crio a função callback ainda vazia, e o valor inicial como zero:
function callback(acumulado, produtoAtual) {
}
const initialValue = 0;
const result = products.reduce(callback, initialValue);
Agora preciso somar o valor de todos os produtos:
O
acumulado
possui o valor inicial na primeira iteração, e o resultado anterior nas próximasE
produtoAtual
será, claro, o produto atualEntão basta eu somar o
acumulado
com o preço do produto atual
function callback(acumulado, produtoAtual) {
return acumulado + produtoAtual.price
}
const initialValue = 0;
const result = products.reduce(callback, initialValue);
console.log(result); // 2150
E assim verá que o preço total desse carrinho é R$ 2150.
Como falei no começo, o reduce talvez seja um dos métodos mais chatinhos de entender. É difícil decidir o momento de usá-lo, já que é complexo e tem múltiplas possibilidades de uso. Uma dessas complexidades é que o reduce pode funcionar no lugar de outros métodos.
O reduce pode substituir outros métodos de array
Olha que flexível.
Os próximos exemplos servem apenas para você compreender as possibilidades do reduce. Mas em todos eles, você verá que usar o outro método é muito mais simples.
Então muito juízo nos usos e:
Guarde o que foi bom
E jogue fora o que restou
Reduce ou map?
O sol nasce, o sapo sapeia, e o map mapeia.
O map é um método Javascript que cria uma array a partir de outra, sem alterar a original.
Para mais detalhes, veja esse artigo sobre map que escrevi no blog.
Com o map, vou pegar uma array com personagens de Dragon Ball, e colocar o nome deles em caixa alta. Mas apenas dos personagens da raça terráqueo
:
const characters = [
{ name: 'Goku', race: 'saiyajin' },
{ name: 'Cell', race: 'android' },
{ name: 'Kuririn', race: 'terráqueo' },
{ name: 'Bulma', race: 'terráqueo' },
{ name: 'Vegeta', race: 'saiyajin' },
{ name: 'Mestre Kame', race: 'terráqueo' },
{ name: 'Kami-Sama', race: 'namekuseijin' }
];
const result = characters.map(function(character) {
if (character.race === 'terráqueo') {
return {
name: character.name.toUpperCase(),
race: character.race
};
} else {
return {
name: character.name,
race: character.race
};
}
});
console.log(result);
/*
[
{ "name": "Goku", "race": "saiyajin" },
{ "name": "Cell", "race": "android" },
{ "name": "KURIRIN", "race": "terráqueo" },
{ "name": "BULMA", "race": "terráqueo" },
{ "name": "Vegeta", "race": "saiyajin" },
{ "name": "MESTRE KAME", "race": "terráqueo" },
{ "name": "Kami-Sama", "race": "namekuseijin" }
]
*/
Eu apenas verifico se a raça do personagem é terráqueo
e faço a operação character.name.toUpperCase()
. Caso contrário, retorno os dados originais.
É possível fazer o mesmo com reduce:
function callback(acumulado, atual) {
if (atual.race === 'terráqueo') {
acumulado.push({
name: atual.name.toUpperCase(),
race: atual.race
})
} else {
acumulado.push({
name: atual.name,
race: atual.race
})
}
return acumulado;
}
const initialValue = [];
const result = characters.reduce(callback, initialValue);
O resultado será exatamente o mesmo. Mas quais as diferenças?
Primeiro, como o resultado será uma array, o
initialValue
é uma array vaziaNo começo da função callback, eu faço a mesma verificação de antes
atual.race === 'terráqueo'
. Mas agora em vez de retornar isso, eu faço um.push()
para dentro da array com os resultadosPor fim, retorno o
acumulado
para que a próxima iteração tenha esse valor para continuar incrementando
Como você pode ver, o map é muito mais simples. Sem contar que é possível aplicar um arrow function e early return para deixar mais simples ainda.
Assim como é possível mapear uma array com o reduce, também é possível filtrar.
Reduce ou filter?
O filter é realmente um filtro, tipo o filtro de café.
Ele filtra uma array e retorna uma lista apenas com os itens que atendem aos requisitos que você definiu.
Veja mais detalhes nesse artigo de como simplificar sua lógica com arrays com o método filter.
Por exemplo, se você tiver uma array com dados de frutas, é possível filtrar e ficar apenas com a vermelhas:
const fruits = [
{ name: 'Maçã', color: 'Vermelha' },
{ name: 'Manga', color: 'Amarela' },
{ name: 'Banana', color: 'Amarela' },
{ name: 'Laranja', color: 'Laranja' },
{ name: 'Uva', color: 'Roxa' },
{ name: 'Pera', color: 'Amarela' },
{ name: 'Morango', color: 'Vermelha' },
{ name: 'Abacaxi', color: 'Amarela' },
{ name: 'Kiwi', color: 'Verde' },
{ name: 'Cereja', color: 'Vermelha' }
];
const result = fruits.filter(function(fruit) {
if (fruit.color === 'Vermelha') {
return true;
} else {
return false;
}
});
console.log(result);
/*
[
{ "name": "Maçã", "color": "Vermelha" },
{ "name": "Morango", "color": "Vermelha" },
{ "name": "Cereja", "color": "Vermelha" }
]
*/
Como previsto, o reduce pode fazer o mesmo:
function callback(acumulado, atual) {
if (atual.color === 'Vermelha') {
acumulado.push(atual)
}
return acumulado;
}
const initialValue = [];
const result = fruits.reduce(callback, initialValue);
Como o reduce faz isso:
Novamente existe o valor inicial com uma array vazia. Mesmo que o resultado contenha zero ou 1 item, a lógica é sempre começar com uma array esse tipo de desafio
A função callback verifica se a fruta é da cor vermelha
Se sim, insere ela no
acumulado
e retornaCaso contrário, retorna o
acumulado
sem adicionar nada, isso acontece com as outras cores de frutas. Por isso a saída final tem menos itens que a array original
Os métodos map e filter sempre retornam uma array, então é fácil imaginar que o valor inicial é uma array vazia. Mas agora os exemplos são com métodos que não, necessariamente, retornam um array.
Reduce ou find?
Esse método é o "CTRL + F" do Javascript.
O find encontra o primeiro item dentro de uma array que atende a um ou mais critérios e retorna esse item.
Veja como ele faz isso, vou usar a mesma array de produtos de antes:
const products = [
{ id: '1a2b3c4d', name: 'Camiseta Básica', color: 'Branca', size: 'M', price: 200 },
{ id: '5e6f7g8h', name: 'Calça Jeans', color: 'Azul', size: 'L', price: 500 },
{ id: '9i0j1k2l', name: 'Bolsa de Couro', color: 'Marrom', size: 'Único', price: 800 },
{ id: '3m4n5o6p', name: 'Tênis Esportivo', color: 'Preto', size: '42', price: 300 },
{ id: '7q8r9s0t', name: 'Vestido Floral', color: 'Rosa', size: 'S', price: 350 }
];
const result = products.find(function(product) {
if (product.id === '3m4n5o6p') {
return true;
} else {
return false;
}
});
console.log(result);
/*
{
id: '3m4n5o6p',
name: 'Tênis Esportivo',
color: 'Preto',
size: '42',
weight: 300,
availability: true
}
*/
Ele encontra o primeiro que possui a propriedade id
igual a 3m4n5o6p
. Após isso, ele finaliza a execução.
E como o reduce faria isso?
function callback(resultado, atual) {
if (atual.id === '3m4n5o6p') {
return atual;
} else {
return resultado;
}
}
const result = products.reduce(callback, null);
Algumas decisões que tomei:
O valor inicial é
undefined
, isso porque se o produto não for encontrado, esse será o valor finalMudei o nome do parâmetro de
acumulado
pararesultado
, pois ela acumula dados a cada iteraçãoDentro da função callback, se o produto
atual
tiver o ID desejado, retorno ele, caso contrário retorno oresultado
A execução só cairá no
if
uma vez (com o produto certo) e noelse
em todas as outras
Veja mais uma versatilidade do reduce, retornar apenas um dos itens da array, e não uma array de itens.
Reduce ou every?
Every em inglês: todo ou toda.
O every responde (com true
ou false
) se todos os itens de uma array atendem aos requisitos. Confira aqui no artigo completo sobre every.
Vou usar uma array com dados de cidades para mostrar do que o every é capaz:
const maioresCidadesBrasil = [
{ nome: 'São Paulo', estado: 'SP', area: 1521 },
{ nome: 'Rio de Janeiro', estado: 'RJ', area: 1200 },
{ nome: 'Brasília', estado: 'DF', area: 5800 },
{ nome: 'Salvador', estado: 'BA', area: 706 },
{ nome: 'Fortaleza', estado: 'CE', area: 314 },
{ nome: 'Belo Horizonte', estado: 'MG', area: 331 },
{ nome: 'Manaus', estado: 'AM', area: 11400 },
{ nome: 'Curitiba', estado: 'PR', area: 435 },
{ nome: 'Recife', estado: 'PE', area: 218 },
{ nome: 'Porto Alegre', estado: 'RS', area: 496 }
];
const result = maioresCidadesBrasil.every(function(cidade) {
if (cidade.area > 300) {
return true;
} else {
return false;
}
});
console.log(result); // false
Esse exemplo mostra como verificar se todas as cidades de uma array possuem área maior que 300 mil km².
A resposta final é false
. Mas se você trocar o if (cidade.area > 300)
para if (cidade.area > 200)
verá que o resultado muda para true
.
Nesse momento o reduce diz "essa é muito fácil, manda outra mais difícil":
function callback(acumulado, cidade) {
const areaMaiorQue300 = cidade.area > 300;
return acumulado && areaMaiorQue300;
}
const result = maioresCidadesBrasil.reduce(callback, true);
A estrutura aqui ficou um pouco diferente:
O valor inicial é
true
, ou seja, eu parto do princípio que todas as cidades tem área acima de 300 mil km². Pois basta que uma cidade não tenha para o resultado final serfalse
Crio a
const areaMaiorQue300
que possui um valor booleanoÉ na linha do
return
que está o segredo. Eu retorno uma operação lógica que tem dois valores booleanos separados por um&&
. Se ambos os valores foremtrue
, a resposta étrue
. Basta que um dos valores sejafalse
para mudar o resultado até a última iteração
Se o reduce ficou mais simples que o outro método? Difícil responder.
Reduce ou some?
Some em inglês: algum ou alguma.
O some afirma se pelo menos um item de uma array atende aos requisitos. Muito parecido com o every.
Vou pegar como exemplo a mesma array de cidades do every. O some pode responder perguntas como "entre as 10 maiores cidades do Brasil, existe alguma da Bahia (BA)?":
const result = maioresCidadesBrasil.some(function(cidade) {
if (cidade.estado === 'BA') {
return true;
} else {
return false;
}
});
console.log(result); // true
A versão "reduce" do some, é bem parecida com a do every:
function callback(acumulado, cidade) {
const cidadeDaBahia = cidade.estado === 'BA';
return acumulado || cidadeDaBahia;
}
const result = maioresCidadesBrasil.reduce(callback, false);
Agora algumas coisas se invertem:
Agora o valor inicial é
false
, pois basta que uma cidade atenda ao requisito para o resultado final sertrue
Na função callback, a
const cidadeDaBahia
também recebe um valor booleano que corresponde a essa afirmaçãoO return também é bem parecido. A diferença é que a operação lógica vem com
||
e não&&
Essas foram as comparações do reduce com outros métodos comuns de array em Javascript.
Até agora, as funções callback dos exemplos receberam apenas dois parâmetros, acumulado
e atual
. Mas existem 4.
Os 4 parâmetros da função callback
Os outros métodos que mencionei podem receber até 3 parâmetros.
Já no reduce, esse número pode chegar a 4. Assim como mostrei nos exemplos, você pode chamá-los como quiser, mas sempre adapte a cada cenário.
Veja agora os parâmetros.
O parâmetro "acumulado"
Esse parâmetro depende do valor inicial apenas na primeira iteração.
Sem o valor inicial, acumulado
é o primeiro item da array. No exemplo abaixo, o valor desse parâmetro é 76
:
const numbers = [ 76, 23, 89, 54 ];
const result = numbers.reduce(function(acumulado, atual) {
// ...
});
Com o valor inicial, acumulado
é esse valor. No exemplo abaixo, o valor é 8
:
const numbers = [ 76, 23, 89, 54 ];
function callback(acumulado, atual) {
// ...
}
const initialValue = 8;
const result = numbers.reduce(callback, initialValue);
Da segunda iteração em diante, ele sempre será o retorno da iteração anterior.
O parâmetro "atual"
Esse parâmetro também depende do valor inicial.
Sem esse valor, atual
é o segundo item da array, 23
:
const numbers = [ 76, 23, 89, 54 ];
const result = numbers.reduce(function(acumulado, atual) {
// ...
});
Com o valor inicial, atual
passa a ser o primeiro item da array, 76
:
const numbers = [ 76, 23, 89, 54 ];
function callback(acumulado, atual) {
// ...
}
const initialValue = 8;
const result = numbers.reduce(callback, initialValue);
Da segunda iteração em diante, ele sempre será o próximo item da array.
O parâmetro "index"
Essa é fácil: ele é um número.
Na primeira iteração, é 0...
Na segunda iteração, é 1...
Na terceira iteração, é 2...
Na quarta iteração, é 3... é isso:
const numbers = [ 76, 23, 89, 54 ];
function callback(acumulado, atual, index) {
console.log(index);
}
const initialValue = 8;
const result = numbers.reduce(callback, initialValue);
/*
0
1
2
3
*/
MAS ALTO LÁ!
Se você não fornecer um valor inicial, não existirá o index 0, e começará diretamente no index 1. Isso acontece porque, como falei lá no começo, sem o valor inicial acontece uma iteração a menos:
const numbers = [ 76, 23, 89, 54 ];
function callback(acumulado, atual, index) {
console.log(index);
}
const result = numbers.reduce(callback);
/*
1
2
3
*/
Você pode usar o parâmetro index para auxiliar em muitos desafios do dia a dia.
Acesse os posts que linkei sobre outros métodos de array. Lá você encontrará diversos exemplo de como usar o parâmetro index.
O parâmetro "array"
Esse é o único parâmetro que não muda de valor.
Ele sempre representa a array que você está iterando. Não recomendo usar esse parâmetro para alterar a array enquanto executa o reduce nela. Apenas faça consultas para outras operações.
Além dos exemplos com o index, nos outros posts você também encontrará desafios com o parâmetro array.
Se você já leu os outros posts dessa série, sabe o que eu sempre digo. Não basta entender o método apenas na teoria, você precisa saber quando aplicar.
Como saber o momento de usar o reduce?
Essa é uma pergunta bem complicada.
Se você leu tudo até agora, primeiramente parabéns. Segundamente, você percebeu que o reduce é um método bastante genérico. Isso porque além de poder substituir outros métodos (map, filter, find, every e some), ele também pode retornar qualquer tipo de dado.
E essa amplitude torna a resposta muito difícil, mas arrisco dizer que para usar o reduce você precisa de 3 coisas.
Você precisa de um dado
Precisar de um dado é algo raro na programação (ironia).
Esse dado pode ser string, number, booleano, objeto ou até outra array.
Mas, já que você precisa manipular dados a todo momento, como saber se deve usar o reduce ou outro método?
Nenhum outro método de array dá conta do problema
Daí sobrou apenas o reduce.
Na minha opinião, quando nenhum outro método de array em Javascript dá conta de gerar esse dado para você, use o reduce.
Ao longo desse post você viu como resolver o mesmo desafio com reduce ou outros métodos. Esses são exemplos onde você não precisa usar o reduce.
Por outro lado, lembra do exemplo de somar o valor total dos produtos em um ecommerce? Esse desafio exige o uso do reduce. Existem outras formas como usar uma iteração com for
, mas prefiro a programação funcional.
Porém, existem casos onde depende. Lembra daquele exemplo da array de produtos? E se você precisar dizer se todos os produtos da array possuem preço acima de R$ 300?
Você pode simplesmente:
Usar o reduce
Definir o valor inicial como
true
E retornar
acumulado &&
um valor booleano que diz se o produto possui preço acima de R$ 300
const products = [
{ id: '1a2b3c4d', name: 'Camiseta Básica', color: 'Branca', size: 'M', price: 200 },
{ id: '5e6f7g8h', name: 'Calça Jeans', color: 'Azul', size: 'L', price: 500 },
{ id: '9i0j1k2l', name: 'Bolsa de Couro', color: 'Marrom', size: 'Único', price: 800 },
{ id: '3m4n5o6p', name: 'Tênis Esportivo', color: 'Preto', size: '42', price: 300 },
{ id: '7q8r9s0t', name: 'Vestido Floral', color: 'Rosa', size: 'S', price: 350 }
];
function callback(acumulado, produtoAtual) {
const produtoAtualAcima300 = produtoAtual.price > 300;
return acumulado && produtoAtualAcima300;
}
const initialValue = true;
const result = products.reduce(callback, initialValue);
console.log(result); // false
Mas se o reduce for muito complicado, você pode concatenar um map com um every:
O map irá converter a array de produtos em uma array que contém apenas os preços dos produtos
O every vai verificar se essa array contém apenas números acima de 300
const result = products
.map(produto => produto.price)
.every(price => price > 300);
console.log(result); // false
Do meu ponto de vista, a lógica é mais fácil de entender, mas exige duas iterações. Já o reduce é mais complicadinho, mas itera uma vez só. Tudo depende.
Você tem uma array em mãos
O último critério para saber se você precisa do reduce é ter uma array.
Isso porque o reduce é um método de array. Não é possível executar ele a partir de uma string, número, booleano ou objeto, a menos que você converta isso para uma array antes.
Callback
Quanta coisa ein...
Recapitulando, o reduce é um método de array em Javascript que reduz uma array a apenas um valor, converte em outra array, manipula, e pode retornar qualquer tipo de dado.
O primeiro parâmetro do reduce é uma função callback que pode receber até 4 parâmetros:
O valor acumulado das últimas iterações
O item atual da array
O index
E a própria array que você está iterando
O segundo parâmetro do reduce é o valor inicial das iterações. Esse é o valor do primeiro parâmetro da função callback na primeira iteração. Se você não informar ele (mas recomendo que sempre faça isso), haverá uma iteração a menos.
Até a penúltima iteração, o retorno da função callback do reduce define qual será o valor do parâmetro acumulado
na próxima. Na última, o retorno da função callback é o próprio return do reduce.
O reduce é um método tão flexível, que pode substituir outros métodos de array em Javascript, como:
map
filter
find
every
some
Saber o momento certo de usar o reduce pode ser um pouco complicado. Mas posso dizer que é quando:
Você precisa de um dado
Nenhum outro método Javascript é capaz de gerar esse dado
Esse dado vem de uma array
Se é possível suar ao escrever um texto tão denso e com um tópico tão difícil de explicar, então eu suei escrevendo esse texto haha.
O reduce é um método que exige conhecimento de vários outros tópicos em Javascript para compreender bem. Então se algo ficou complicado, volte aqui depois de um tempo, garanto que você irá perceber coisas novas.
Esse é o último post de uma série de 6 artigos sobre métodos de array em Javascript. Linkei todos os outros 5 foram aqui em algum momento do texto.
Obrigado pela sua leitura. Deixe um comentário se ficou com dúvidas.
Se esse artigo te ajudou de alguma forma, e ficou com vontade de dizer um obrigado, compartilhe com um amigo ou nas redes sociais.
Até a próxima!