Quem tem medo do reduce? Entenda esse método agora ou seu dinheiro de volta

Veja os diferentes tipos de operações que o reduce pode aplicar em uma array. Entenda esse método agora, ou devolvo seu dinheiro.

  • reduce
  • array
  • javascript
Front-end

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:

array com os números 76, 23, 89 e 54 sendo reduzida ao número 242 através de uma soma
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, e atual o segundo

  • Nas próximas, acumulado é o retorno das iterações anteriores, e oatual é o próximo item da array

  • Apesar de a array ter 4 itens, houve apenas 3 iterações (vou mostrar como corrigir isso)

Tabela de iterações do reduce
IteraçãoacumuladoatualRetorno (a soma dos números)
1762399
29989188
318854242

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:

Tabela de iterações do reduce com valor inicial
IteraçãoacumuladoatualRetorno (a soma dos números)
187684
28423107
310789196
419654250
  • 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); e atual representa o primeiro item da array, 76

  • Nas próximas, acumulado é o resultado das iterações anteriores, e o atual é 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:

tabela mostrando que o retorno da função callback de uma iteração define o valor do parâmetro acumulado na próxima

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óximas

  • E produtoAtual será, claro, o produto atual

  • Entã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

NX Zero

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 vazia

  • No 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 resultados

  • Por 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 retorna

    • Caso 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 final

  • Mudei o nome do parâmetro de acumulado para resultado, pois ela acumula dados a cada iteração

  • Dentro da função callback, se o produto atual tiver o ID desejado, retorno ele, caso contrário retorno o resultado

  • A execução só cairá no if uma vez (com o produto certo) e no else 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 ser false

  • 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 forem true, a resposta é true. Basta que um dos valores seja false 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 ser true

  • Na função callback, a const cidadeDaBahia também recebe um valor booleano que corresponde a essa afirmação

  • O 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!

Veja outros posts sobre Front-end