10 funções Lodash importantes para desenvolvedores JavaScript

Para desenvolvedores de JavaScript, Lodash dispensa apresentações. No entanto, a biblioteca é vasta e muitas vezes parece opressiva. Não mais!

Lodash, Lodash, Lodash. . . por onde eu começo! 🤔

Houve um tempo em que o ecossistema JavaScript era incipiente; poderia ser comparado ao oeste selvagem ou a uma selva, se você quiser, onde muita coisa estava acontecendo, mas havia muito poucas respostas para as frustrações e produtividade diárias do desenvolvedor.

Então Lodash entrou em cena, e parecia uma inundação que submergiu tudo. Desde necessidades simples do dia a dia, como classificação, até transformações complexas de estruturas de dados, Lodash veio carregado (sobrecarregado, até!) Com funcionalidades que transformaram a vida dos desenvolvedores JS em pura felicidade.

Olá, Lodas!

E onde está Lodash hoje? Bem, ele ainda tem todas as vantagens que oferecia inicialmente, e mais algumas, mas parece ter perdido a participação na comunidade JavaScript. Por quê? Posso pensar em alguns motivos:

  • Algumas funções na biblioteca Lodash eram (e ainda são) lentas quando aplicadas a listas grandes. Embora isso nunca tenha afetado 95% dos projetos existentes, desenvolvedores influentes dos 5% restantes deram a Lodash uma má impressão e o efeito caiu em cascata nas bases.
  • Há uma tendência no ecossistema JS (pode até dizer a mesma coisa sobre o pessoal da Golang) em que a arrogância é mais comum do que o necessário. Portanto, confiar em algo como Lodash é visto como estúpido e é derrubado em fóruns como StackOverflow quando as pessoas sugerem tais soluções (“O quê?! Usar uma biblioteca inteira para algo assim? Posso combinar filter() com reduce() para obter a mesma coisa em uma função simples!”).
  • Lodash é velho. Pelo menos pelos padrões JS. Foi lançado em 2012, então, no momento em que escrevo, já se passaram quase dez anos. A API tem estado estável, e não é possível adicionar muitas coisas interessantes todos os anos (simplesmente porque não há necessidade), o que gera tédio para o desenvolvedor JS superexcitado médio.

Na minha opinião, não usar o Lodash é uma perda significativa para nossas bases de código JavaScript. Ele provou ser livre de bugs e soluções elegantes para problemas cotidianos que encontramos no trabalho, e usá-lo apenas tornará nosso código mais legível e fácil de manter.

Com isso dito, vamos mergulhar em algumas das funções Lodash comuns (ou não!) E ver como essa biblioteca é incrivelmente útil e bonita.

Clone . . . profundamente!

Como os objetos são passados ​​por referência em JavaScript, isso cria uma dor de cabeça para os desenvolvedores quando desejam clonar algo com a esperança de que o novo conjunto de dados seja diferente.

let people = [
  {
    name: 'Arnold',
    specialization: 'C++',
  },
  {
    name: 'Phil',
    specialization: 'Python',
  },
  {
    name: 'Percy',
    specialization: 'JS',
  },
];

// Find people writing in C++
let folksDoingCpp = people.filter((person) => person.specialization == 'C++');

// Convert them to JS!
for (person of folksDoingCpp) {
  person.specialization = 'JS';
}

console.log(folksDoingCpp);
// [ { name: 'Arnold', specialization: 'JS' } ]

console.log(people);
/*
[
  { name: 'Arnold', specialization: 'JS' },
  { name: 'Phil', specialization: 'Python' },
  { name: 'Percy', specialization: 'JS' }
]
*/

Observe como, em nossa inocência pura e apesar de nossas boas intenções, a matriz original de pessoas sofreu uma mutação no processo (a especialização de Arnold mudou de C++ para JS) — um grande golpe para a integridade do sistema de software subjacente! De fato, precisamos de uma maneira de fazer uma cópia verdadeira (profunda) do array original.

Olá Dave, conheça Dave!

Talvez você possa argumentar que esta é uma maneira “boba” de codificar em JS; no entanto, a realidade é um pouco complicada. Sim, temos o adorável operador de desestruturação disponível, mas qualquer um que tenha tentado desestruturar objetos e arrays complexos conhece a dor. Então, existe a ideia de usar serialização e desserialização (talvez JSON) para obter cópias profundas, mas isso apenas torna seu código mais confuso para o leitor.

Por outro lado, veja como incrivelmente elegante e concisa é a solução quando Lodash é usado:

const _ = require('lodash');

let people = [
  {
    name: 'Arnold',
    specialization: 'C++',
  },
  {
    name: 'Phil',
    specialization: 'Python',
  },
  {
    name: 'Percy',
    specialization: 'JS',
  },
];

let peopleCopy = _.cloneDeep(people);

// Find people writing in C++
let folksDoingCpp = peopleCopy.filter(
  (person) => person.specialization == 'C++'
);

// Convert them to JS!
for (person of folksDoingCpp) {
  person.specialization = 'JS';
}

console.log(folksDoingCpp);
// [ { name: 'Arnold', specialization: 'JS' } ]

console.log(people);
/*
[
  { name: 'Arnold', specialization: 'C++' },
  { name: 'Phil', specialization: 'Python' },
  { name: 'Percy', specialization: 'JS' }
]
*/

Observe como o array de pessoas permanece intocado após a clonagem profunda (Arnold ainda é especializado em C++ neste caso). Mas o mais importante, o código é simples de entender.

  Noções básicas sobre conformidade SOC 1 vs SOC 2 vs SOC 3

Remover duplicatas de uma matriz

Remover duplicatas de um array soa como um excelente problema de entrevista/quadro branco (lembre-se, em caso de dúvida, jogue um hashmap no problema!). E, claro, você sempre pode escrever uma função personalizada para fazer isso, mas e se você encontrar vários cenários diferentes para tornar seus arrays únicos? Você pode escrever várias outras funções para isso (e correr o risco de encontrar bugs sutis), ou pode apenas usar o Lodash!

Nosso primeiro exemplo de arrays exclusivos é bastante trivial, mas ainda representa a velocidade e a confiabilidade que o Lodash traz para a mesa. Imagine fazer isso escrevendo toda a lógica personalizada 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 ]

Observe que a matriz final não está classificada, o que obviamente não é motivo de preocupação aqui. Mas agora, vamos imaginar 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 Lodash!

const _ = require('lodash');

const users = [
  { id: 10, name: 'Phil', age: 32 },
  { id: 8, name: 'Jason', age: 44 },
  { id: 11, name: 'Rye', age: 28 },
  { id: 10, name: 'Phil', age: 32 },
];

const uniqueUsers = _.uniqBy(users, 'id');
console.log(uniqueUsers);
/*
[
  { id: 10, name: 'Phil', age: 32 },
  { id: 8, name: 'Jason', age: 44 },
  { id: 11, name: 'Rye', age: 28 }
]
*/

Neste exemplo, usamos o método uniqBy() para dizer ao Lodash que queremos que os objetos sejam únicos na propriedade id. Em uma linha, expressamos o que poderia levar de 10 a 20 linhas e introduzimos mais espaço para bugs!

Há muito mais coisas disponíveis sobre como tornar as coisas únicas em Lodash, e eu encorajo você a dar uma olhada no documentos.

Diferença de duas matrizes

União, diferença, etc., podem soar como termos que é melhor deixar para trás em palestras enfadonhas do ensino médio sobre a Teoria dos Conjuntos, mas eles aparecem com mais frequência do que nunca na prática cotidiana. É comum ter uma lista e querer mesclar outra lista com ela ou querer descobrir quais elementos são exclusivos dela em comparação com outra lista; para esses cenários, a função de diferença é perfeita.

Olá, A. Tchau, B!

Vamos começar a jornada da diferença tomando um cenário simples: você recebeu uma lista de todos os IDs de usuários do sistema, bem como uma lista daqueles 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 acontece em um cenário mais realista, você tiver que trabalhar com uma matriz de objetos em vez de simples primitivos? Bem, Lodash tem um bom método differenceBy() para isso!

const allUsers = [
  { id: 1, name: 'Phil' },
  { id: 2, name: 'John' },
  { id: 3, name: 'Rogg' },
];
const activeUsers = [
  { id: 1, name: 'Phil' },
  { id: 2, name: 'John' },
];
const inactiveUsers = _.differenceBy(allUsers, activeUsers, 'id');
console.log(inactiveUsers);
// [ { id: 3, name: 'Rogg' } ]

Legal, né?!

Assim como a diferença, existem outros métodos em Lodash para operações de conjuntos comuns: união, interseção, etc.

Achatando matrizes

A necessidade de nivelar arrays surge com bastante frequência. Um caso de uso é que você recebeu uma resposta da API e precisa aplicar alguma combinação map() e filter() em uma lista complexa de objetos/arrays aninhados para extrair, digamos, IDs de usuário, e agora você fica com matrizes de matrizes. Aqui está um trecho de código que descreve essa situação:

const orderData = {
  internal: [
    { userId: 1, date: '2021-09-09', amount: 230.0, type: 'prepaid' },
    { userId: 2, date: '2021-07-07', amount: 130.0, type: 'prepaid' },
  ],
  external: [
    { userId: 3, date: '2021-08-08', amount: 30.0, type: 'postpaid' },
    { userId: 4, date: '2021-06-06', amount: 330.0, type: 'postpaid' },
  ],
};

// find user ids that placed postpaid orders (internal or external)
const postpaidUserIds = [];

for (const [orderType, orders] of Object.entries(orderData)) {
  postpaidUserIds.push(orders.filter((order) => order.type === 'postpaid'));
}
console.log(postpaidUserIds);

Você consegue adivinhar como é postPaidUserIds agora? Dica: é nojento!

[
  [],
  [
    { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' },
    { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' }
  ]
]

Agora, se você for uma pessoa sensata, não deseja escrever uma lógica personalizada para extrair os objetos do pedido e colocá-los bem em uma linha dentro de uma matriz. Basta usar o método flatten() e aproveitar as uvas:

const flatUserIds = _.flatten(postpaidUserIds);
console.log(flatUserIds);
/*
[
  { userId: 3, date: '2021-08-08', amount: 30, type: 'postpaid' },
  { userId: 4, date: '2021-06-06', amount: 330, type: 'postpaid' }
]
*/

Observe que flatten() vai apenas um nível de profundidade. Ou seja, se seus objetos estiverem presos em dois, três ou mais níveis de profundidade, flatten() eles irão desapontá-lo. Nesses casos, Lodash tem o método flattenDeep(), mas esteja avisado que a aplicação desse método em estruturas muito grandes pode tornar as coisas mais lentas (pois nos bastidores, há uma operação recursiva em ação).

  Como mover notas do seu iPhone para o iCloud

O objeto/array está vazio?

Graças à forma como valores e tipos “falsos” funcionam em JavaScript, às vezes algo tão simples quanto verificar o vazio resulta em pavor existencial.

Como você verifica se um array está vazio? Você pode verificar se seu comprimento é 0 ou não. Agora, como você verifica se um objeto está vazio? Bem… espere um minuto! É aqui que a sensação desconfortável se instala, e aqueles exemplos de JavaScript contendo coisas como [] == false e {} == false começam a circular nossas cabeças. Quando sob pressão para fornecer um recurso, minas terrestres como essas são a última coisa de que você precisa – elas tornarão seu código difícil de entender e introduzirão incerteza em seu conjunto de testes.

Trabalhando com dados ausentes

No mundo real, os dados nos escutam; não importa o quanto o desejemos, raramente é simplificado e são. Um exemplo típico é a falta de objetos/arrays nulos em uma grande estrutura de dados recebida como resposta da API.

Suponha que recebemos o seguinte objeto como uma resposta da API:

const apiResponse = {
  id: 33467,
  paymentRefernce: 'AEE3356T68',
  // `order` object missing
  processedAt: `2021-10-10 00:00:00`,
};

Conforme mostrado, geralmente obtemos um objeto de pedido na resposta da API, mas nem sempre é o caso. Então, e se tivermos algum código que dependa desse objeto? Uma maneira seria codificar defensivamente, mas dependendo de quão aninhado é o objeto de pedido, logo estaríamos escrevendo um código muito feio se quisermos evitar erros de tempo de execução:

if (
  apiResponse.order &&
  apiResponse.order.payee &&
  apiResponse.order.payee.address
) {
  console.log(
    'The order was sent to the zip code: ' +
      apiResponse.order.payee.address.zipCode
  );
}

🤢🤢 Sim, muito feio de escrever, muito feio de ler, muito feio de manter, e por aí vai. Felizmente, Lodash tem uma maneira direta de lidar com essas situações.

const zipCode = _.get(apiResponse, 'order.payee.address.zipCode');
console.log('The order was sent to the zip code: ' + zipCode);
// The order was sent to the zip code: undefined

Há também a opção fantástica de fornecer um valor padrão em vez de ficar indefinido para itens ausentes:

const zipCode2 = _.get(apiResponse, 'order.payee.address.zipCode', 'NA');
console.log('The order was sent to the zip code: ' + zipCode2);
// The order was sent to the zip code: NA

Não sei você, mas get() é uma daquelas coisas que me trazem lágrimas de felicidade. Não é nada chamativo. Não há sintaxe consultada ou opções para memorizar, mas veja a quantidade de sofrimento coletivo que isso pode aliviar! 😇

Debouncing

Caso você não esteja familiarizado, debouncing é um tema comum no desenvolvimento de front-end. A ideia é que às vezes é benéfico iniciar uma ação não imediatamente, mas depois de algum tempo (geralmente, alguns milissegundos). O que isso significa? Aqui está um exemplo.

Imagine um site de comércio eletrônico com uma barra de pesquisa (bem, qualquer site/aplicativo da web hoje em dia!). Para uma melhor UX, não queremos que o usuário tenha que apertar enter (ou pior, pressionar o botão “pesquisar”) para mostrar sugestões/pré-visualizações com base em seu termo de pesquisa. Mas a resposta óbvia é um pouco carregada: se adicionarmos um ouvinte de evento a onChange() para a barra de pesquisa e dispararmos uma chamada de API para cada pressionamento de tecla, teremos criado um pesadelo para nosso back-end; haveria muitas chamadas desnecessárias (por exemplo, se “pincel de carpete branco” for pesquisado, haverá um total de 18 solicitações!) e quase todas elas serão irrelevantes porque a entrada do usuário não foi concluída.

A resposta está no debouncing, e a ideia é esta: não envie uma chamada de API assim que o texto mudar. Aguarde algum tempo (digamos, 200 milissegundos) e, se nesse momento houver outro pressionamento de tecla, cancele a contagem de tempo anterior e comece a esperar novamente. Como resultado, é somente quando o usuário faz uma pausa (seja porque está pensando ou porque terminou e espera alguma resposta) que enviamos uma solicitação de API para o back-end.

A estratégia geral que descrevi é complicada e não vou me aprofundar na sincronização do gerenciamento e cancelamento do cronômetro; no entanto, o processo real de debounce é muito simples se você estiver usando o Lodash.

const _ = require('lodash');
const axios = require('axios');

// This is a real dogs' API, by the way!
const fetchDogBreeds = () =>
  axios
    .get('https://dog.ceo/api/breeds/list/all')
    .then((res) => console.log(res.data));

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 1000); // after one second
debouncedFetchDogBreeds(); // shows data after some time

Se você está pensando em setTimeout(), eu teria feito o mesmo trabalho, bem, há mais! O debounce de Lodash vem com muitos recursos poderosos; por exemplo, você pode querer garantir que o debounce não seja indefinido. Ou seja, mesmo se houver um pressionamento de tecla toda vez que a função estiver prestes a ser disparada (cancelando assim o processo geral), convém garantir que a chamada da API seja feita de qualquer maneira após, digamos, dois segundos. Para isso, Lodash debounce() possui a opção maxWait:

const debouncedFetchDogBreeds = _.debounce(fetchDogBreeds, 150, { maxWait: 2000 }); // debounce for 250ms, but send the API request after 2 seconds anyway

Confira o oficial documentos para um mergulho mais profundo. Eles estão cheios de coisas super importantes!

  Como definir um limite de tempo de aplicativo no iPhone e iPad

Remover valores de uma matriz

Não sei você, mas odeio escrever código para remover itens de um array. Primeiro, tenho que obter o índice do item, verificar se o índice é realmente válido e, em caso afirmativo, chamar o método splice() e assim por diante. Nunca consigo me lembrar da sintaxe e, portanto, preciso pesquisar as coisas o tempo todo e, no final, fico com a sensação incômoda de que deixei algum bug estúpido aparecer.

const greetings = ['hello', 'hi', 'hey', 'wave', 'hi'];
_.pull(greetings, 'wave', 'hi');
console.log(greetings);
// [ 'hello', 'hey' ]

Observe duas coisas:

  • A matriz original foi alterada no processo.
  • O método pull() remove todas as instâncias, mesmo se houver duplicatas.
  • Há outro método relacionado chamado pullAll() que aceita um array como segundo parâmetro, facilitando a remoção de vários itens de uma só vez. É verdade que poderíamos usar pull() com um operador spread, mas lembre-se de que Lodash surgiu em uma época em que o operador spread nem era uma proposta na linguagem!

    const greetings2 = ['hello', 'hi', 'hey', 'wave', 'hi'];
    _.pullAll(greetings2, ['wave', 'hi']);
    console.log(greetings2);
    // [ 'hello', 'hey' ]

    Último índice de um elemento

    O método nativo indexOf() do JavsScript é legal, exceto quando você está interessado em escanear o array na direção oposta! E mais uma vez, sim, você poderia simplesmente escrever um loop decrescente e encontrar o elemento, mas por que não usar uma técnica muito mais elegante?

    Aqui está uma solução Lodash rápida usando o método lastIndexOf():

    const integers = [2, 4, 1, 6, -1, 10, 3, -1, 7];
    const index = _.lastIndexOf(integers, -1);
    console.log(index); // 7

    Infelizmente, não há nenhuma variante desse método em que possamos pesquisar objetos complexos ou até mesmo passar uma função de pesquisa personalizada.

    Fecho eclair. Descompactar!

    A menos que você tenha trabalhado em Python, zip/unzip é um utilitário que você nunca notará ou imaginará em toda a sua carreira como desenvolvedor de JavaScript. E talvez por uma boa razão: raramente há o tipo de necessidade desesperada de compactar/descompactar como existe para filter(), etc. No entanto, é um dos melhores utilitários menos conhecidos e pode ajudá-lo a criar um código sucinto em algumas situações .

    Ao contrário do que parece, zip/unzip não tem nada a ver com compactação. Em vez disso, é uma operação de agrupamento em que arrays do mesmo comprimento podem ser convertidos em um único array de arrays com elementos na mesma posição compactados juntos (zip()) e de volta (unzip()). Sim, eu sei, está ficando confuso tentar se contentar com palavras, então vamos ver um pouco de código:

    const animals = ['duck', 'sheep'];
    const sizes = ['small', 'large'];
    const weight = ['less', 'more'];
    
    const groupedAnimals = _.zip(animals, sizes, weight);
    console.log(groupedAnimals);
    // [ [ 'duck', 'small', 'less' ], [ 'sheep', 'large', 'more' ] ]

    As três matrizes originais foram convertidas em uma única com apenas duas matrizes. E cada uma dessas novas matrizes representa um único animal com todos os seus em um só lugar. Assim, o índice 0 nos diz que tipo de animal é, o índice 1 nos diz seu tamanho e o índice 2 nos diz seu peso. Como resultado, os dados agora são mais fáceis de trabalhar. Depois de aplicar as operações necessárias nos dados, você pode dividi-los novamente usando unzip() e enviá-los de volta à fonte original:

    const animalData = _.unzip(groupedAnimals);
    console.log(animalData);
    // [ [ 'duck', 'sheep' ], [ 'small', 'large' ], [ 'less', 'more' ] ]

    O utilitário compactar/descompactar não é algo que mudará sua vida da noite para o dia, mas mudará sua vida um dia!

    Conclusão 👨‍🏫

    (Coloquei todo o código-fonte usado neste artigo aqui para você experimentar diretamente do navegador!)

    O Lodash documentos estão repletos de exemplos e funções que vão te surpreender. Em uma época em que o masoquismo parece estar aumentando no ecossistema JS, Lodash é como uma lufada de ar fresco, e eu recomendo fortemente que você use esta biblioteca em seus projetos!