Para desenvolvedores que utilizam JavaScript, a biblioteca Lodash é amplamente conhecida. No entanto, sua extensão pode, por vezes, intimidar. Mas isso acaba agora!
Lodash, Lodash, Lodash… por onde começar? 🤔
Houve um período em que o universo JavaScript era ainda muito novo, comparável a uma região inexplorada, onde muitas coisas aconteciam, mas faltavam soluções para as dificuldades e necessidades diárias dos desenvolvedores.
Foi então que a Lodash surgiu, como uma onda que cobriu tudo. Desde tarefas simples do dia a dia, como ordenação, até transformações complexas de estruturas de dados, a Lodash trouxe uma vasta gama de funcionalidades, tornando a vida dos desenvolvedores JS muito mais fácil e agradável.
Olá, Lodash!
E onde a Lodash se encontra hoje? Bem, ela mantém todas as vantagens que oferecia inicialmente, com adições, mas parece ter perdido espaço na comunidade JavaScript. Por quê? Algumas razões me vêm à mente:
- Algumas funções da biblioteca Lodash eram (e ainda são) mais lentas ao serem aplicadas a listas extensas. Apesar de isso não afetar a maioria dos projetos, desenvolvedores influentes de uma pequena parcela tiveram uma má impressão da Lodash, o que se espalhou pela comunidade.
- Há uma tendência no ecossistema JS, onde a arrogância é mais comum do que o necessário. Portanto, depender de algo como a Lodash é visto como um sinal de fraqueza e é criticado em fóruns como o StackOverflow quando as pessoas sugerem essas soluções. “O quê?! Usar uma biblioteca inteira para algo assim? Posso combinar `filter()` com `reduce()` para obter o mesmo resultado com uma função simples!”.
- A Lodash é antiga, pelo menos segundo os padrões do JS. Lançada em 2012, já se passaram mais de dez anos. A API está estável e não há muitas coisas novas para adicionar anualmente, o que causa um certo tédio para o desenvolvedor JS médio, sempre em busca de novidades.
Na minha opinião, não usar a Lodash é uma grande perda para nossas bases de código JavaScript. Ela oferece soluções comprovadamente livres de erros para problemas cotidianos que encontramos no trabalho, além de tornar o código mais legível e fácil de manter.
Dito isso, vamos explorar algumas funções comuns (e outras nem tanto) da Lodash e ver como essa biblioteca pode ser extremamente útil e eficiente.
Clonagem Profunda
Em JavaScript, objetos são passados por referência, o que causa problemas quando se deseja clonar algo, esperando que o novo conjunto de dados seja independente do original.
let pessoas = [ { nome: 'Arnold', especializacao: 'C++', }, { nome: 'Phil', especializacao: 'Python', }, { nome: 'Percy', especializacao: 'JS', }, ]; // Encontrar pessoas que programam em C++ let devsCpp = pessoas.filter((pessoa) => pessoa.especializacao == 'C++'); // Converter para JS! for (pessoa of devsCpp) { pessoa.especializacao = 'JS'; } console.log(devsCpp); // [ { nome: 'Arnold', especializacao: 'JS' } ] console.log(pessoas); /* [ { nome: 'Arnold', especializacao: 'JS' }, { nome: 'Phil', especializacao: 'Python' }, { nome: 'Percy', especializacao: 'JS' } ] */
Note como, apesar de nossas boas intenções, a matriz original de pessoas foi alterada (a especialização de Arnold mudou de C++ para JS) – um grande problema para a integridade do sistema! Precisamos de uma maneira de fazer uma cópia verdadeira (profunda) da matriz original.
Talvez você possa argumentar que esta é uma forma “ingênua” de programar em JS; no entanto, a realidade é um pouco mais complicada. Sim, temos o operador de desestruturação disponível, mas quem já tentou desestruturar objetos e arrays complexos conhece as dificuldades. Há também a ideia de usar serialização e desserialização (talvez com JSON) para obter cópias profundas, mas isso torna o código mais confuso para o leitor.
Por outro lado, veja como a solução é elegante e concisa quando a Lodash é utilizada:
const _ = require('lodash'); let pessoas = [ { nome: 'Arnold', especializacao: 'C++', }, { nome: 'Phil', especializacao: 'Python', }, { nome: 'Percy', especializacao: 'JS', }, ]; let pessoasCopia = _.cloneDeep(pessoas); // Encontrar pessoas que programam em C++ let devsCpp = pessoasCopia.filter( (pessoa) => pessoa.especializacao == 'C++' ); // Converter para JS! for (pessoa of devsCpp) { pessoa.especializacao = 'JS'; } console.log(devsCpp); // [ { nome: 'Arnold', especializacao: 'JS' } ] console.log(pessoas); /* [ { nome: 'Arnold', especializacao: 'C++' }, { nome: 'Phil', especializacao: 'Python' }, { nome: 'Percy', especializacao: 'JS' } ] */
Observe como a matriz de pessoas permanece inalterada após a clonagem profunda (Arnold ainda é especialista em C++ neste caso). E o mais importante, o código é fácil de entender.
Remoção de Duplicatas de Matrizes
Remover duplicatas de um array parece um bom problema para uma entrevista técnica (em caso de dúvida, use um hashmap!). Claro, você pode escrever sua própria função, mas e se você tiver várias situações diferentes para tornar seus arrays únicos? Você pode escrever várias funções para isso (correndo o risco de bugs), ou usar a Lodash!
Nosso primeiro exemplo de arrays únicos é trivial, mas mostra a velocidade e confiabilidade da Lodash. Imagine escrever toda a lógica personalizada por você mesmo!
const _ = require('lodash'); const userIds = [12, 13, 14, 12, 5, 34, 11, 12]; const uniqueUserIds = _.uniq(userIds); console.log(uniqueUserIds); // [ 12, 13, 14, 5, 34, 11 ]
Note que a matriz final não está ordenada, o que não é um problema aqui. Mas imagine um cenário mais complicado: temos uma matriz de usuários que extraímos de algum lugar, mas queremos garantir que ela contenha apenas usuários únicos. Fácil com a Lodash!
const _ = require('lodash'); const usuarios = [ { id: 10, nome: 'Phil', idade: 32 }, { id: 8, nome: 'Jason', idade: 44 }, { id: 11, nome: 'Rye', idade: 28 }, { id: 10, nome: 'Phil', idade: 32 }, ]; const uniqueUsers = _.uniqBy(usuarios, 'id'); console.log(uniqueUsers); /* [ { id: 10, nome: 'Phil', idade: 32 }, { id: 8, nome: 'Jason', idade: 44 }, { id: 11, nome: 'Rye', idade: 28 } ] */
Neste exemplo, usamos o método `uniqBy()` para dizer à Lodash que queremos que os objetos sejam únicos pela propriedade `id`. Em uma linha, expressamos algo que poderia levar de 10 a 20 linhas e introduzir mais espaço para erros!
Há muitas outras maneiras de lidar com elementos únicos na Lodash, e eu encorajo você a dar uma olhada na documentação.
Diferença Entre Duas Matrizes
União, diferença, etc., podem parecer termos que é melhor deixar para trás em aulas chatas do ensino médio sobre a Teoria dos Conjuntos, mas aparecem com mais frequência do que nunca no trabalho diário. É comum ter uma lista e querer juntar outra lista a ela ou saber quais elementos são exclusivos em comparação com outra lista. Para esses casos, a função de diferença é perfeita.
Vamos começar com um cenário simples: você recebeu uma lista de todos os IDs de usuários do sistema e outra com aqueles cujas contas estão ativas. Como você encontra os IDs inativos? Simples, certo?
const _ = require('lodash'); const allUserIds = [1, 3, 4, 2, 10, 22, 11, 8]; const activeUserIds = [1, 4, 22, 11, 8]; const inactiveUserIds = _.difference(allUserIds, activeUserIds); console.log(inactiveUserIds); // [ 3, 2, 10 ]
E se, como em um cenário mais realista, você tiver que trabalhar com uma matriz de objetos em vez de valores simples? Bem, a Lodash tem um método `differenceBy()` para isso!
const allUsers = [ { id: 1, nome: 'Phil' }, { id: 2, nome: 'John' }, { id: 3, nome: 'Rogg' }, ]; const activeUsers = [ { id: 1, nome: 'Phil' }, { id: 2, nome: 'John' }, ]; const inactiveUsers = _.differenceBy(allUsers, activeUsers, 'id'); console.log(inactiveUsers); // [ { id: 3, nome: 'Rogg' } ]
Legal, né?!
Assim como a diferença, existem outros métodos na Lodash para operações de conjuntos comuns: união, interseção, etc.
Achatando Matrizes
A necessidade de achatar arrays surge com frequência. Um exemplo é receber dados de uma API e precisar aplicar `map()` e `filter()` em uma lista complexa de objetos e arrays aninhados para extrair IDs de usuário, resultando em arrays de arrays. Aqui está um exemplo:
const dadosPedido = { interno: [ { userId: 1, data: '2021-09-09', valor: 230.0, tipo: 'pré-pago' }, { userId: 2, data: '2021-07-07', valor: 130.0, tipo: 'pré-pago' }, ], externo: [ { userId: 3, data: '2021-08-08', valor: 30.0, tipo: 'pós-pago' }, { userId: 4, data: '2021-06-06', valor: 330.0, tipo: 'pós-pago' }, ], }; // encontrar IDs de usuários que fizeram pedidos pós-pagos (internos ou externos) const userIdsPosPago = []; for (const [tipoPedido, pedidos] of Object.entries(dadosPedido)) { userIdsPosPago.push(pedidos.filter((pedido) => pedido.tipo === 'pós-pago')); } console.log(userIdsPosPago);
Consegue adivinhar como `userIdsPosPago` está agora? Dica: está confuso!
[ [], [ { userId: 3, data: '2021-08-08', valor: 30, tipo: 'pós-pago' }, { userId: 4, data: '2021-06-06', valor: 330, tipo: 'pós-pago' } ] ]
Se você for sensato, não vai querer escrever uma lógica personalizada para extrair os objetos do pedido e colocá-los em uma única matriz. Use o método `flatten()` e aproveite!
const flatUserIds = _.flatten(userIdsPosPago); console.log(flatUserIds); /* [ { userId: 3, data: '2021-08-08', valor: 30, tipo: 'pós-pago' }, { userId: 4, data: '2021-06-06', valor: 330, tipo: 'pós-pago' } ] */
Note que `flatten()` só funciona em um nível de profundidade. Ou seja, se seus objetos estiverem aninhados em dois, três ou mais níveis, ele não será suficiente. Nesses casos, a Lodash tem o método `flattenDeep()`, mas tenha cuidado, pois aplicá-lo em estruturas muito grandes pode ser lento (já que há uma operação recursiva nos bastidores).
O Objeto/Array Está Vazio?
Devido ao comportamento de valores e tipos “falsos” em JavaScript, verificar se algo está vazio pode ser complicado.
Como você verifica se um array está vazio? Verificando se o comprimento é 0 ou não. Mas como você verifica se um objeto está vazio? Aqui as coisas ficam estranhas, e exemplos de JavaScript com `[] == false` e `{} == false` começam a aparecer. Sob pressão para entregar funcionalidades, essas armadilhas são a última coisa que você precisa – tornam o código difícil de entender e introduzem incertezas nos testes.
Lidando com Dados Ausentes
No mundo real, os dados não são perfeitos. Um exemplo típico é a falta de objetos ou arrays nulos em uma grande estrutura de dados recebida de uma API.
Suponha que recebemos o seguinte objeto de uma API:
const apiResposta = { id: 33467, referenciaPagamento: 'AEE3356T68', // objeto `pedido` ausente processadoEm: `2021-10-10 00:00:00`, };
Geralmente, obtemos um objeto de pedido na resposta da API, mas nem sempre é o caso. E se tivermos código que depende desse objeto? Uma solução seria codificar defensivamente, mas dependendo de quão aninhado está o objeto de pedido, teríamos um código muito feio para evitar erros de tempo de execução:
if ( apiResposta.pedido && apiResposta.pedido.pagador && apiResposta.pedido.pagador.endereco ) { console.log( 'O pedido foi enviado para o CEP: ' + apiResposta.pedido.pagador.endereco.cep ); }
🤢🤢 Muito feio de escrever, ler e manter. Felizmente, a Lodash tem uma maneira simples de lidar com isso.
const cep = _.get(apiResposta, 'pedido.pagador.endereco.cep'); console.log('O pedido foi enviado para o CEP: ' + cep); // O pedido foi enviado para o CEP: undefined
E existe a opção de fornecer um valor padrão para itens ausentes, em vez de receber `undefined`:
const cep2 = _.get(apiResposta, 'pedido.pagador.endereco.cep', 'NA'); console.log('O pedido foi enviado para o CEP: ' + cep2); // O pedido foi enviado para o CEP: NA
O método `get()` é muito útil. Ele é simples, sem sintaxe complicada ou opções para memorizar, mas veja quanto sofrimento ele pode aliviar! 😇
Debouncing
O debouncing é um conceito comum no desenvolvimento front-end. A ideia é iniciar uma ação não imediatamente, mas após um certo tempo (geralmente, alguns milissegundos). Veja um exemplo.
Imagine um site de e-commerce com uma barra de pesquisa. Para uma melhor experiência do usuário, não queremos que ele precise apertar enter para ver sugestões/pré-visualizações. Mas a solução óbvia é problemática: se adicionarmos um ouvinte de evento `onChange()` para a barra de pesquisa e fizermos uma chamada à API para cada tecla pressionada, teremos um pesadelo para o back-end; haveria muitas chamadas desnecessárias (por exemplo, se alguém pesquisar por “escova de dentes elétrica”, haverá 21 chamadas!), e quase todas seriam irrelevantes porque a entrada do usuário não foi concluída.
A solução é o debouncing: não enviar uma chamada à API assim que o texto mudar. Espere um tempo (digamos, 200 milissegundos) e, se nesse período houver outro pressionamento de tecla, cancele o tempo anterior e comece a esperar novamente. Como resultado, só enviamos uma solicitação à API quando o usuário faz uma pausa.
A estratégia geral que descrevi é complexa, mas o processo real de debouncing é muito simples com a Lodash.
const _ = require('lodash'); const axios = require('axios'); // Esta é uma API de raças de cães! const buscarRacasCachorro = () => axios .get('https://dog.ceo/api/breeds/list/all') .then((res) => console.log(res.data)); const debouncedBuscarRacasCachorro = _.debounce(buscarRacasCachorro, 1000); // após um segundo debouncedBuscarRacasCachorro(); // mostra os dados após um tempo
Se você pensou em usar `setTimeout()`, saiba que há mais! O debounce da Lodash tem recursos poderosos. Por exemplo, você pode querer garantir que o debounce não fique indefinido. Ou seja, mesmo se houver um pressionamento de tecla a cada momento que a função está prestes a ser executada (cancelando o processo), você pode querer garantir que a chamada à API seja feita após, digamos, dois segundos. Para isso, `debounce()` tem a opção `maxWait`:
const debouncedBuscarRacasCachorro = _.debounce(buscarRacasCachorro, 150, { maxWait: 2000 }); // debounce por 150ms, mas envia a requisição após 2 segundos
Veja a documentação oficial para mais detalhes. Ela é repleta de informações úteis!
Removendo Valores de um Array
Não sei você, mas odeio escrever código para remover itens de um array. Preciso obter o índice do item, verificar se ele é válido e, se for, chamar o método `splice()`, etc. Nunca me lembro da sintaxe, preciso pesquisar o tempo todo e no final, tenho a sensação de ter deixado algum bug.
const cumprimentos = ['olá', 'oi', 'ei', 'aceno', 'oi']; _.pull(cumprimentos, 'aceno', 'oi'); console.log(cumprimentos); // [ 'olá', 'ei' ]
Observe duas coisas:
- A matriz original foi alterada.
- O método `pull()` remove todas as ocorrências, mesmo as duplicadas.
Há outro método chamado `pullAll()` que aceita um array como segundo parâmetro, facilitando a remoção de vários itens de uma vez. Poderíamos usar `pull()` com o operador spread, mas lembre-se de que a Lodash surgiu numa época em que o operador spread ainda não existia!
const cumprimentos2 = ['olá', 'oi', 'ei', 'aceno', 'oi']; _.pullAll(cumprimentos2, ['aceno', 'oi']); console.log(cumprimentos2); // [ 'olá', 'ei' ]
Último Índice de um Elemento
O método nativo `indexOf()` do JavaScript é útil, exceto quando você quer procurar no array na direção oposta! Você pode escrever um loop decrescente, mas por que não usar uma técnica mais elegante?
Aqui está uma solução rápida com o método `lastIndexOf()` da Lodash:
const numeros = [2, 4, 1, 6, -1, 10, 3, -1, 7]; const index = _.lastIndexOf(numeros, -1); console.log(index); // 7
Infelizmente, não há uma variante desse método que permita pesquisar objetos complexos ou passar uma função de pesquisa personalizada.
Zip e Unzip
Se você não trabalhou com Python, zip/unzip são funcionalidades que você talvez nunca tenha notado ou imaginado em sua carreira como desenvolvedor JavaScript. E talvez por um bom motivo: raramente há necessidade de compactar ou descompactar como para `filter()`. No entanto, é uma das ferramentas menos conhecidas e pode ajudar a criar um código conciso em algumas situações.
Ao contrário do que parece, zip/unzip não tem nada a ver com compactação. É uma operação de agrupamento em que arrays do mesmo tamanho são convertidos em um único array de arrays com elementos na mesma posição agrupados (`zip()`) e desfeitos (`unzip()`).
const animais = ['pato', 'ovelha']; const tamanhos = ['pequeno', 'grande']; const pesos = ['leve', 'pesado']; const animaisAgrupados = _.zip(animais, tamanhos, pesos); console.log(animaisAgrupados); // [ [ 'pato', 'pequeno', 'leve' ], [ 'ovelha', 'grande', 'pesado' ] ]
As três matrizes originais foram convertidas em uma única com duas matrizes. Cada uma dessas novas matrizes representa um animal com seus atributos em um só lugar. O índice 0 nos diz o tipo de animal, o índice 1 o tamanho e o índice 2 o peso. Os dados agora são mais fáceis de manipular. Depois de aplicar as operações necessárias nos dados, você pode separá-los novamente usando `unzip()`:
const dadosAnimal = _.unzip(animaisAgrupados); console.log(dadosAnimal); // [ [ 'pato', 'ovelha' ], [ 'pequeno', 'grande' ], [ 'leve', 'pesado' ] ]
O utilitário zip/unzip não mudará sua vida da noite para o dia, mas será útil algum dia!
Conclusão 👨🏫
Você pode experimentar o código deste artigo aqui diretamente do navegador!
A documentação da Lodash está repleta de exemplos e funções incríveis. Em um momento em que o masoquismo parece estar crescendo no ecossistema JS, a Lodash é como um sopro de ar fresco. Recomendo fortemente que você use essa biblioteca em seus projetos!