Reduce vai reduzir TODOS os seus problemas a apenas um: como usar o reduce?
- Sant’Anna, Israel (2021)

Tudo bem, TODOS é claramente um exagero, mas o método reduce é capaz de resolver MUITOS problemas. Inclusive, a funcionalidade de outros métodos, como map, filter, find, some, every e vários outros (todos talvez?) pode ser implementada utilizando o reduce!

A princípio a sintaxe e o funcionamento do reduce pode parecer complicada e difícil, mas é mais fácil do que parece. Vamos por partes!

SINTAXE

O método reduce pode receber dois argumentos:

[].reduce(reducer[, valor inicial]);

PosiçãoArgumentoObrigatório/OpcionalObservações
1função reducerObrigatório 
2valor inicialOpcionalSe fornecido que será passado para a função reducer

REDUCER

A função reducer recebe de dois a quatro argumentos, sendo:

const reducer = (acc, curr[, index[, originalArray]]) => { ... };

PosiçãoArgumentoObrigatório/OpcionalObservações
1acumulador (valorAnterior) (retorno)Obrigatórioo valor resultante da chamada anterior da função reducer
2valorAtualObrigatórioo valor do item na chamada atual
3indexAtualOpcionalo index do item na chamada atual
4arrayOriginalOpcionalo array onde o método reduce foi chamado

Para cada item do array, a função reducer é executada. Por exemplo:

reduce - exemplo 01

A saída no console será:

acc: 1
curr: 2
acc: 3
curr: 3
6

Ué?! Mas se a função é executada para cada item do array, porque o valor do primeiro item não é logado (curr: 1)?

Isso acontece porque quando não utilizamos o segundo argumento do método reduce (o valor inicial), a função reducer atribui ao acumulador o valor do primeiro item, e já segue operando do segundo item em diante. Para evitar isso, devemos passar um valor inicial, como:

reduce - exemplo 02

E a saída no console será:

acc: 0
curr: 1
acc: 1
curr: 2
acc: 3
curr: 3
6

E por que não é logado o último valor do acumulador (acc: 6)? Isso acontece porque quando o método reduce já avaliou o último item, a função reducer não é executada novamente, e o valor do acumulador é retornado, sendo atribuído, neste caso à constante sum.

Esse exemplo é bem simples e não chega perto do poder que o reduce dá a nós desenvolvedores! Vamos mostrar alguns exemplos um pouco mais complexos e na próxima seção explicar cada um passo a passo! :D

EXEMPLOS

Transformar um array em objeto:

reduce - exemplo 03

Transformar um objeto em array:

reduce - exemplo 04

Verificar uma condição e retornar apenas true/false:

reduce - exemplo 05

Remover itens duplicados de um array:

reduce - exemplo 06

Mesclar dois arrays em um só, na ordem original:

reduce - exemplo 07

Mesclar dois arrays em um só, um após o outro:

Iterando pelo menor array:
reduce - exemplo 08
Iterando pelo maior array:
reduce - exemplo 09

Mesclar dois (ou mais) arrays em um objeto:

reduce - exemplo 10

PASSO A PASSO

Alguns dos exemplos anteriores podem ser fáceis de entender, mas para que fique bem claro, vamos passo a passo em cada um, extraindo e expandindo a função reducer para entender melhor o que está acontecendo.

Transformar um array em objeto:

reduce - exemplo 11

1: name e age são variáveis obtidas através da desestruturação do elemento atual

Se observarmos bem, cada elemento de peopleArray é um array onde o primeiro elemento (peopleArray[0]) é o nome, e o segundo elemento (peopleArray[1]) é a idade.

Quando desestruturamos, já atribuímos um nome a cada elemento deste array, logo se não tivéssemos desestruturado, mas usado, por exemplo, uma variável chamada curr, poderíamos atribuir as variáveis desta forma:

const name = curr[0] (que é o mesmo que peopleArray[0])
const age = curr[1] (que é o mesmo que peopleArray[1])

2: Para criar o objeto utilizamos o index como chave e as propriedades name e age.

Como index é variável, precisamos usar a notação de colchetes (bracket notation) para que a chave não seja literalmente ‘index’, mas sim, a cada iteração index é avaliado e a chave é corretamente atribuída.

Se as chaves deste objeto mais interno tivessem outro nome, não poderíamos utilizar a abreviação nos nomes de propriedade (property shorthand).

3: Object.assign() é uma forma de se atribuir propriedades a um objeto.

Funciona basicamente assim:

Ele recebe dois argumentos, nesta ordem: target e source.
As propriedades de source são copiadas para target.
Se a propriedade já existia, será sobrescrita em target.
Se não existia, será adicionada a target.
O retorno deste método é target já modificado com as propriedades novas;

Mais detalhes e exemplos disponíveis na documentação.

4: Por fim, retornamos o acumulador com o novo objeto (personObject) incluído na chave especificada (index).

Lembrando que a função reducer vai ser chamada para cada item do array, então a cada iteração um novo objeto vai ser criado e incluído no acumulador!

Exemplo:

Iteração 1:
acc: {}
name: Pedro
age: 20
index: 0
personObject: 0: { name: 'Pedro', age: 20 }

Iteração 2:
acc: { 0: { name: 'Pedro', age: 20 } }
name: Tiago
age: 25
index: 1
personObject: 1: { name: 'Tiago', age: 25 }

Iteração 3:
acc: {
  0: { name: 'Pedro', age: 20 },
  1: { name: 'Tiago', age: 25 }
}
name: João
age: 30
index: 2
personObject: 2: { name: 'João', age: 30 }

Resultado final:
acc: {
  0: { name: 'Pedro', age: 20 },
  1: { name: 'Tiago', age: 25 },
  2: { name: 'João', age: 30 }
}

5: Incluímos um objeto vazio como valor inicial, pois caso contrário o primeiro item do array seria utilizado na primeira iteração e não obteríamos o resultado esperado.
(Experimente e veja o que acontece! :D)

Transformar um objeto em array:

reduce - exemplo 12

1: A cada iteração, o elemento atual será posto dentro de um novo array e concatenado ao acumulador.

Exemplo:

Iteração 1:
acc: []
curr: ['israelss', 'israelss.github.io']
O que será concatenado: [ ['israelss', 'israelss.github.io'] ]

Iteração 2:
acc: [ ['israelss', 'israelss.github.io'] ]
curr: ['trybe', 'betrybe.com']
O que será concatenado: [ ['trybe', 'betrybe.com'] ]

Resultado final:
acc: [ ['israelss', 'israelss.github.io'], ['trybe', 'betrybe.com'] ]

Isso é necessário para que o resultado seja um array de arrays, pois o método concat “extrai” um nível do array.

Mais detalhes e exemplos disponíveis na documentação.

Verificar uma condição e retornar apenas true/false:

reduce - exemplo 13

1/5: A cada iteração, será primeiro verificado se o acumulador é true ou false. Se for true será retornado o valor resultante da segunda expressão (um condicional ternário). Se for false a segunda expressão nem é avaliada e este é o valor retornado nesta iteração. Como, nesta implementação, é impossível mudar de false para true, uma vez que o acumulador se tornar false este será o resultado final retornado pela função reducer.

2 (Função evenReducer): Caso o item atual seja par, o acumulador será true (3), caso contraŕio, o acumulador será false (4).

6 (Função oddsReducer): Caso o item atual seja ímpar, o acumulador será true (7), caso contraŕio, o acumulador será false (8).

Exemplo:
isAllEven1:

Iteração 1:
acc: true
curr: 2
O que será retornado: true

Iteração 2:
acc: true
curr: 4
O que será retornado: true

Iteração 3:
acc: true
curr: 6
O que será retornado: true

Resultado final:
acc: true

isAllOdds3:

Iteração 1:
acc: true
curr: 1
O que será retornado: true

Iteração 2:
acc: true
curr: 2
O que será retornado: false

Iteração 3:
acc: true
curr: 3
O que será retornado: false

Resultado final:
acc: false

Repare que mesmo 3 sendo ímpar, o condicional ternário não é avaliado, porque o acumulador já é false.

Remover itens duplicados de um array:

reduce - exemplo 14

1: A cada iteração, será avaliado se o acumulador contém o elemento atual. Se contiver, o acumulador será retornado sem nenhuma modificação (2). Se não contiver, o elemento atual será concatenado ao acumulador e o array resultante é retornado (3).

Exemplo:

Iteração 1:
acc: []
curr: 1
O que será retornado: [1]

Iteração 2:
acc: [1]
curr: 1
O que será retornado: [1]

Iteração 3:
acc: [1]
curr: 2
O que será retornado: [1, 2]

Iteração 4:
acc: [1, 2]
curr: 3
O que será retornado: [1, 2, 3]

Iteração 5:
acc: [1, 2, 3]
curr: 4
O que será retornado: [1, 2, 3, 4]

Iteração 6:
acc: [1, 2, 3, 4]
curr: 4
O que será retornado: [1, 2, 3, 4]

Iteração 7:
acc: [1, 2, 3, 4]
curr: 5
O que será retornado: [1, 2, 3, 4, 5]

Resultado final:
acc: [1, 2, 3, 4, 5]

Mesclar dois arrays em um só, na ordem original:

reduce - exemplo 15

1: A cada iteração, será concatenado ao acumulador o elemento atual do array de origem.

2: Em seguida, será concatenado ao acumulador o elemento com o mesmo índice do atual, porém este elemento pertence ao segundo array. Por isso é necessário utilizar o argumento index, para que seja possível acessar a mesma posição em outro array

3: O acumulador com os dois elementos, em ordem, é retornado.

Exemplo:

Iteração 1:
acc: []
curr: 1
index: 0
O que será concatenado (1): 1
O que será concatenado (2): 4

Iteração 2:
acc: [1, 4]
curr: 2
index: 1
O que será concatenado (1): 2
O que será concatenado (2): 5

Iteração 3:
acc: [1, 4, 2, 5]
curr: 3
index: 2
O que será concatenado (1): 3
O que será concatenado (2): 6

Resultado final:
acc: [1, 4, 2, 5, 3, 6]

Mesclar dois arrays em um só, um após o outro:

Iterando pelo menor array:
reduce - exemplo 16

1: Para este exemplo não utilizamos nem o acumulador, nem o elemento atual de cada iteração!

2: A cada iteração, adicionamos ao array original o elemento do maior array que tem o mesmo index que o elemento atual.

3: O acumulador neste caso vai ser o array original, modificado a cada iteração.

Exemplo:

Iteração 1:
acc: []
index: 0
array: [1, 2, 3]
O que será concatenado: 4

Iteração 2:
acc: [1, 2, 3, 4]
index: 1
array: [1, 2, 3, 4]
O que será concatenado: 5

Iteração 3:
acc: [1, 2, 3, 4, 5]
index: 2
array: [1, 2, 3, 4, 5]
O que será concatenado: 6

Resultado final:
array: [1, 2, 3, 4, 5, 6]

Vale lembrar que normalmente, o método reduce não altera o array original, mas nesta implementação o array original é alterado!

Iterando pelo maior array:
reduce - exemplo 17

1: Assim como no exemplo anterior, neste não utilizamos nem o acumulador, nem o elemento atual de cada iteração!

2: Diferente do exemplo anterior, nós precisamos incluir essa verificação pois como estamos iterando pelo maior array, vai chegar um momento que não vai existir o elemento do menor array que tem o mesmo index que o elemento atual. Mas enquanto esse momento não chega, a cada iteração, adicionamos ao array original o elemento do menor array que tem o mesmo index que o elemento atual.

3: O acumulador neste caso também vai ser o array original, modificado a cada iteração.

Exemplo:

Iteração 1:
acc: []
index: 0
array: [4, 5, 6, 7]
O que será concatenado: 1

Iteração 2:
acc: [4, 5, 6, 7, 1]
index: 1
array: [4, 5, 6, 7, 1]
O que será concatenado: 2

Iteração 3:
acc: [4, 5, 6, 7, 1, 2]
index: 2
array: [4, 5, 6, 7, 1, 2]
O que será concatenado: 3

Iteração 4:
acc: [4, 5, 6, 7, 1, 2, 3]
index: 3
array: [4, 5, 6, 7, 1, 2, 3]
O que será concatenado: nada (arrayFive[3] === undefined, então não satisfaz a condição necessária para que haja o push (2))

Resultado final:
array: [4, 5, 6, 7, 1, 2, 3]

Vale lembrar que, assim como no exemplo anterior, o array original é alterado!

Mesclar dois (ou mais) arrays em um objeto:

reduce - exemplo 18

A primeira parte deste exemplo tem o mesmo efeito do primeiro exemplo (Transformar um array em objeto), então vamos passar rápido por ela e focar mais na segunda parte.

1: O argumento index será utilizado como chave do objeto a ser adicionado.

2: Como não estamos atuando em cima de um array só, não podemos usar aqui a desestruturação e precisamos definir o valor da chave name manualmente.

3: O mesmo se aplica à chave age. E aqui utilizamos novamente o argumento index, para pegar corretamente o elemento do array que contém as idades.

Exemplo:
peopleDataObject1:

Iteração 1:
acc: {}
index: 0
curr: Pedro
agesArray[0]: 20
O que será incluído no acumulador: 0: { name: 'Pedro', age: 20 }

Iteração 2:
acc: { 0: { name: 'Pedro', age: 20 } }
index: 1
curr: Tiago
agesArray[1]: 25
O que será incluído no acumulador: 1: { name: 'Tiago', age: 25 }

Iteração 3:
acc: {
  0: { name: 'Pedro', age: 20 },
  1: { name: 'Tiago', age: 25 }
}
index: 2
curr: João
agesArray[2]: 30
O que será incluído no acumulador: 2: { name: 'João', age: 30 }

Resultado final:
acc: {
  0: { name: 'Pedro', age: 20 },
  1: { name: 'Tiago', age: 25 },
  2: { name: 'João', age: 30 }
}

Na segunda parte usamos o argumento index para iterar “simultâneamente” pelos três arrays.

E antes de utilizar o Object.assign() para retornar o acumulador com o novo objeto inserido, definimos algumas constantes para podermos utilizar a abreviação nos nomes de propriedade (property shorthand) e simplificar um pouco o código.

1: O argumento curr será utilizado como a chave name.

2: O elemento do array agesArray que tem o mesmo index que o elemento atual será utilizado como a chave age.

3: O elemento do array citiesArray que tem o mesmo index que o elemento atual será utilizado como a chave city.

4: Utilizamos as contantes definidas nos passos anteriores para montar uma string em uma template literal.

5: Agora utilizamos o Object.assign() para retornar o novo objeto criado usando abreviação nos nomes de propriedade (property shorthand) e inserido no acumulador.

Exemplo:

Para brevidade, o acumulador e o objeto criado não serão explicitados no exemplo abaixo, mas a essa altura você já deve conseguir entender quais serão estes valores a cada iteração.

peopleDataObject2:

Iteração 1:
index: 0
curr: Pedro
agesArray[0]: 20
citiesArray[0]: Rio de Janeiro

Iteração 2:
index: 1
curr: Tiago
agesArray[1]: 25
citiesArray[1]: São Paulo

Iteração 3:
index: 2
curr: João
agesArray[2]: 30
citiesArray[2]: Belo Horizonte

Resultado final:
acc: {
   0: {
    name: 'Pedro',
    age: 20,
    city: 'Rio de Janeiro',
    greeting: 'Olá, meu nome é Pedro,
          tenho 20 anos e
          sou de Rio de Janeiro.',
   },
   1: {
    name: 'Tiago',
    age: 25,
    city: 'São Paulo',
    greeting: 'Olá, meu nome é Tiago,
          tenho 25 anos e
          sou de São Paulo.',
   },
   2: {
    name: 'João',
    age: 30,
    city: 'Belo Horizonte',
    greeting: 'Olá, meu nome é João,
          tenho 30 anos e
          sou de Belo Horizonte.',
   }
  }

CONCLUSÃO

Estes são só alguns exemplos do que pode ser feito com o reduce.

Mas é bom salientar que não é porque podemos fazer algo com uma ferramenta que devemos utilizá-la. Existem formas mais adequadas de se realizar algumas tarefas, como por exemplo utilizar flatMap em vez de reduce em conjunto com concat em algumas situações.

Tendo dito isto, fica o convite para utilizar mais o método reduce nos seus projetos. Leia com carinho e calma a excelente documentação sobre o reduce disponível na MDN, e qualquer dúvida, sugestão ou correção, deixe um comentário abaixo! Até o próximo artigo!