Array - filter e map - JavaScript - TypeScript - Node.js
Como filtrar ou mapear um novo array utilizando Array.filter e Array.map em JavaScript - Node.js - TypeScript. Veja como estas funções funcionam e reduza suas linhas de código.
Estas funções, filter
e map
com certeza são as funções que mais utilizo quando trabalho com arrays em JavaScript e o motivo é bem simples, chegar no mesmo resultado com menos linhas de códigos.
Sem mais delongas, vamos considerar a seguinte massa de dados para os exemplos abordados neste artigo:
const dados = [
{ id: 1, nome: 'Nome 1', valor: 10 },
{ id: 2, nome: 'Nome 2', valor: 20 },
{ id: 3, nome: 'Nome 3', valor: 30 },
{ id: 4, nome: 'Nome 4', valor: 40 },
{ id: 5, nome: 'Nome 5', valor: 50 },
];
JavaScript - Array.prototype.map()
Quando utilizar: tenho um array e gostaria de transformá-lo em um outro array.
Exemplo 1 - array de objetos para array de string
Vamos supor que seja necessário transformar o array dados
(Array<any>
) em um array de string
(Array<string>
) onde cada posição seja igual a propriedade nome
de cada item do array dados
. O resultado final seria:
[ 'Nome 1', 'Nome 2', 'Nome 3', 'Nome 4', 'Nome 5' ]
Para chegar no resultado acima podemos utilizar um loop for
:
const novoArray = [];
for (let i = 0; i < dados.length; i++) {
novoArray.push(dados[i].nome);
}
Como alternativa podemos utilizar a função map()
para chegar no mesmo resultado porém com uma redução significativa de 4 linhas de código para apenas 1:
const novoArray = dados.map(item => item.nome);
Exemplo 2 - array de objeto A para array de objeto B
O array dados
tem 3 informações para cada item. Vamos utilizar novamente um loop for
para criar um novo array com apenas 2 informações:
id
nome
+valor
- nome concatenado com o valor
const novoArray = [];
for (let i = 0; i < dados.length; i++) {
const item = dados[i];
novoArray.push({
id: item.id,
nome: `${item.nome} - ${item.valor}`
});
}
console.log(novoArray);
Obtendo exatamente o mesmo resultado utilizando o método .map()
:
const novoArray = dados.map(item => {
return {
id: item.id,
nome: `${item.nome} - ${item.valor}`
};
});
console.log(novoArray);
Apesar de ser um exemplo bem simples, suponha que sua API faça uma consulta a uma outra API que retorne 10 campos. Sua API deve enviar para o frontend apenas 3 campos porque os outros 7 não serão utilizados. Veja que neste caso o .map
pode ajudar.
Ideia por trás do Array.prototype.map()
Para entender como o map()
funciona, vamos construir uma função chamada meuMap
que retorne o mesmo resultado do map
. Primeiro vamos criar uma nova função que seja aplicada a todo Array
:
Array.prototype.meuMap = function() {
console.log('olá! dados do array: ', JSON.stringify(this));
};
// Agora todo array poderá utilizar a função meuMap
[1, 2, 'marcelo'].meuMap();
// Resultado no console:
// olá! dados do array: – "[1,2,\"marcelo\"]"
Veja que o this
dentro da função meuMap
nos dá acesso a todos os itens do array. É possível observar este comportamento no resultado do console, também acima.
Vamos evoluir este código retirando este console.log
e retornando um novo array com o mesmo conteúdo do array de origem:
Array.prototype.meuMap = function() {
const novoArray = [];
// loop em cima do array original (this)
for (let i = 0; i < this.length; i++) {
novoArray.push(this[i]);
}
return novoArray;
};
// Agora todo array poderá utilizar a função meuMap
const teste = dados.meuMap();
console.log(teste);
Apesar se estarmos retornando um novo array, o resultado final tem o mesmo formato do array de origem. Então como customizar este retorno para modificar o array de saída? ou em outras palavras, como transformar o array de origem em um outro array?
Para fazer isto podemos parametrizar uma função que receba um objeto e retorne outro objeto, algo do tipo:
// Criamos um novo parâmetro (funcaoTransformacao):
Array.prototype.meuMap = function(funcaoTransformacao) {
const novoArray = [];
for (let i = 0; i < this.length; i++) {
// Para cada item do array, chamaremos a
// funcaoTransformacao:
const thisTransformado = funcaoTransformacao(this[i]);
novoArray.push(thisTransformado);
}
return novoArray;
};
function retornaNome(entrada) {
return entrada.nome;
}
// Agora todo array poderá utilizar a função meuMap
const teste = dados.meuMap(retornaNome);
console.log(teste);
// Resultado no console:
// ["Nome 1", "Nome 2", "Nome 3", "Nome 4", "Nome 5"]
Como alternativa, podemos remover a declaração explicíta function retornaNome
e utilizar uma arrow function:
Veja que chegamos em como a função .map()
deve funcionar, então vamos avançar mais um pouco nesta função falando do índice.
Índice - index
O .map()
também dá acesso ao índice, ou seja, é como se ele passasse para a "função de transformação" o i
do for
. Pegando carona no exemplo acima, a ideia é a seguinte:
Array.prototype.meuMap = function(funcaoTransformacao) {
const novoArray = [];
for (let i = 0; i < this.length; i++) {
// ****************************************************
// Veja que agora passamos o "i" além do item do array:
// ****************************************************
const thisTransformado = funcaoTransformacao(this[i], i);
novoArray.push(thisTransformado);
}
return novoArray;
};
// ************************************
// Agora a função recebe também o index
// ************************************
function retornaNome(entrada, index) {
return entrada.nome + ' - i: ' + index;
}
// Agora todo array poderá utilizar a função meuMap
const teste = dados.meuMap(retornaNome);
console.log(teste);
// Resultado no console:
// [
// "Nome 1 - i: 0",
// "Nome 2 - i: 1",
// "Nome 3 - i: 2",
// "Nome 4 - i: 3",
// "Nome 5 - i: 4"
// ]
Então podemos concluir que o .map()
faz um loop em cima do array e para cada item ele chama uma função que é passada como parâmetro no (Array.map(funcao, índice <opcional>)
).
Exemplo 3 - construindo um array para mock
Aqui vai um script bem legal que sempre utilizo quando preciso de uma massa (array) rápida para testar alguma funcionalidade:
console.log(Array(10).fill('').map((a, i) => `Item ${++i}`));
// resultado:
// [
// "Item 1",
// "Item 2",
// "Item 3",
// "Item 4",
// "Item 5",
// "Item 6",
// "Item 7",
// "Item 8",
// "Item 9",
// "Item 10"
// ]
// ou também
const novoArray = Array(10)
.fill('')
.map((a, i) => {
return {
id: ++i,
nome: `Item ${++i}`
}
});
console.log(novoArray);
// Resultado
// [
// {id: 1, nome: "Item 2"},
// {id: 2, nome: "Item 3"},
// {id: 3, nome: "Item 4"},
// {id: 4, nome: "Item 5"},
// {id: 5, nome: "Item 6"},
// {id: 6, nome: "Item 7"},
// {id: 7, nome: "Item 8"},
// {id: 8, nome: "Item 9"},
// {id: 9, nome: "Item 10"},
// {id: 10, nome: "Item 11"}
// ]
JavaScript Array.prototype.filter()
Quando utilizar: tenho um array e preciso selecionar apenas alguns itens deste array gerando um novo array.
Exemplo 1 - filtrando registros com id par
Vamos filtrar o array dados
pegando apenas os registros que tenham um id
par. Primeiramente vamos utilizar um loop for
:
const novoArray = [];
for (let i = 0; i < dados.length; i++) {
if (dados[i].id % 2 === 0) {
novoArray.push(dados[i].nome);
}
}
Agora utilizando o .filter
para obter o mesmo resultado:
const novoArray = dados.filter(item => item.id % 2 === 0);
Independente do for
ou do filter
o resultado será exatamente o mesmo, novamente a diferença está na quantidade de código:
[{
id: 2,
nome: "Nome 2",
valor: 20
}, {
id: 4,
nome: "Nome 4",
valor: 40
}]
Ideia por trás do filter()
Vamos pegar uma "carona" no exemplo do map()
para construir nossa própria função se baseando na ideia ideia do filter
:
Array.prototype.meuFilter = function() {
const novoArray = [];
for (let i = 0; i < this.length; i++) {
// ************************
// TODO Como vamos filtrar?
// ************************
novoArray.push(this[i]);
}
return novoArray;
};
O trecho de código acima mostra que estamos criando uma função para os nossos arrays chamada meuFilter
. Então todo array terá acesso a esta função. O problema é que nossa função ainda não filtra nada porque precisamos de uma condição que esteja parametrizada para atender aos diferentes cenários, ou seja, as vezes quero filtrar os itens cujo o id
seja par, ou filtrar itens cujo o nome
comece com a letra 'm'
e assim por diante.
Então vamos adicionar um parametro que irá filtrar o array:
// *******************************************************
// Adicionamos um novo parâmetro que representa uma função
// que receberá alguma coisa e devolverá true ou false
// *******************************************************
Array.prototype.meuFilter = function(funcaoFiltro) {
const novoArray = [];
for (let i = 0; i < this.length; i++) {
// ************************************************
// Veja que o this[i] (item do array de origem)
// será incorporado ao retorno apenas se a condição
// do funcaoFiltro for satisfeita
// ************************************************
if (funcaoFiltro(this[i])) {
novoArray.push(this[i]);
}
}
return novoArray;
};
// ****************************************************************
// Testando nossa função meuFilter utilizando array function inline
// ****************************************************************
const novoArray = dados.meuFilter(item => item.id % 2 === 0);
// ou
function meuFiltro(item) {
return item.id % 2 === 0;
}
const novoArray = dados.meuFilter(meuFiltro);
Veja que o resultado utilizando o meuFilter
é exatamente o mesmo que utilizando o filter
.
[{
id: 2,
nome: "Nome 2",
valor: 20
}, {
id: 4,
nome: "Nome 4",
valor: 40
}]
O objetivo aqui foi mostrar como a função filter
funciona, pois se você souber replicá-la, com certeza saberá utilizá-la, certo?
Considerações
Abordamos apenas 2 funções que o objeto Array
nos fornece mas existem diversas outras que podem ajudar em outros cenários: reduce
, flat
, sort
, find
, some
, entre outras.
Apesar de não abordado, o método filter
também possuí um segundo parâmetro em seu callback para retornar o índice, exatamente como no map
. Também vale mencionar que ambos, filter
e map
, recebem como parâmetro uma função (callback) que recebe 3 parâmetros:
// Callback é a função que passamos como parâmetro
// no filter ou map.
// Este callback recebe os 3 parâmetros conforme abaixo.
dados.filter((item, indice, arrayOriginal) => {
// TODO
});
dados.map((item, indice, arrayOriginal) => {
// TODO
});
Caso você não utilize o indice
e o arrayOrignal
conforme código acima, é possível omití-los conforme os exemplos abordados neste artigo.
Uma outra coisa interessante é que podemos combinar a utilização destas funções:
const resultado = dados
.filter(a => a.id < 2)
.map((a, i) => {
return {
id: a.id,
index: i,
nomeValor: a.nome + ' ' + a.valor
};
});
// Resultado:
[{
id: 1,
index: 0,
nomeValor: "Nome 1 10"
}]
Links interessantes:
- https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array
- https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/map
- https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
- https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Functions/Arrow_functions