10 funciones importantes de Lodash para desarrolladores de JavaScript

Para los desarrolladores de JavaScript, Lodash no necesita presentación. Sin embargo, la biblioteca es enorme y, a menudo, se siente abrumadora. ¡Ya no!

Lodash, Lodash, Lodash. . . ¡por dónde empiezo! 🤔

Hubo un tiempo en que el ecosistema de JavaScript era incipiente; podría compararse con el salvaje oeste o una jungla, por así decirlo, donde estaban sucediendo muchas cosas, pero había muy pocas respuestas para las frustraciones y la productividad diarias de los desarrolladores.

Después Lodash entró en escena, y se sintió como una inundación que sumergió todo. Desde simples necesidades cotidianas como la clasificación hasta transformaciones de estructuras de datos complejas, Lodash vino cargado (¡sobrecargado, incluso!) Con funcionalidad que convirtió la vida de los desarrolladores de JS en pura felicidad.

¡Hola, Lodash!

¿Y dónde está Lodash hoy? Bueno, todavía tiene todas las ventajas que ofrecía inicialmente, y algunas más, pero parece haber perdido participación en la comunidad de JavaScript. ¿Por qué? Puedo pensar en algunas razones:

  • Algunas funciones de la biblioteca de Lodash eran (y siguen siendo) lentas cuando se aplicaban a listas grandes. Si bien esto nunca habría afectado al 95 % de los proyectos, los desarrolladores influyentes del 5 % restante le dieron a Lodash una mala prensa y el efecto cayó en cascada hacia las bases.
  • Hay una tendencia en el ecosistema JS (incluso podría decir lo mismo sobre la gente de Golang) donde la arrogancia es más común de lo necesario. Entonces, confiar en algo como Lodash se considera estúpido y se rechaza en foros como StackOverflow cuando las personas sugieren tales soluciones («¿Qué ?! ¿Usar una biblioteca completa para algo como esto? Puedo combinar filter() con reduce() para lograr lo mismo en una función simple!”).
  • Lodash es viejo. Al menos para los estándares JS. Salió en 2012, así que al momento de escribir, han pasado casi diez años. La API ha sido estable, y no se pueden agregar muchas cosas emocionantes cada año (simplemente porque no es necesario), lo que genera aburrimiento para el desarrollador de JS promedio sobreexcitado.

En mi opinión, no usar Lodash es una pérdida significativa para nuestras bases de código de JavaScript. Ha probado soluciones elegantes y libres de errores para los problemas cotidianos con los que nos encontramos en el trabajo, y usarlo solo hará que nuestro código sea más legible y mantenible.

Dicho esto, profundicemos en algunas de las funciones comunes (¡o no!) de Lodash y veamos cuán increíblemente útil y hermosa es esta biblioteca.

clonar . . ¡profundamente!

Dado que los objetos se pasan por referencia en JavaScript, crea un dolor de cabeza para los desarrolladores cuando desean clonar algo con la esperanza de que el nuevo conjunto de datos sea 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 cómo en nuestra pura inocencia y a pesar de nuestras buenas intenciones, la matriz de personas original mutó en el proceso (la especialización de Arnold cambió de C++ a JS), ¡un gran golpe para la integridad del sistema de software subyacente! De hecho, necesitamos una forma de hacer una copia verdadera (profunda) de la matriz original.

¡Hola Dave, conoce a Dave!

Tal vez pueda argumentar que esta es una forma «tonta» de codificar en JS; sin embargo, la realidad es un poco complicada. Sí, tenemos disponible el encantador operador de desestructuración, pero cualquiera que haya intentado desestructurar matrices y objetos complejos conoce el dolor. Luego, está la idea de utilizar la serialización y la deserialización (quizás JSON) para lograr una copia profunda, pero solo hace que el código sea más complicado para el lector.

Por el contrario, mire cuán asombrosamente elegante y concisa es la solución cuando se usa Lodash:

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 cómo la matriz de personas no se modifica después de la clonación profunda (Arnold aún se especializa en C++ en este caso). Pero lo que es más importante, el código es fácil de entender.

Eliminar duplicados de una matriz

Eliminar duplicados de una matriz suena como un excelente problema de entrevista/pizarrón (recuerde, en caso de duda, ¡arroje un hashmap al problema!). Y, por supuesto, siempre puede escribir una función personalizada para hacer eso, pero ¿qué pasa si encuentra varios escenarios diferentes en los que hacer que sus arreglos sean únicos? Podría escribir varias otras funciones para eso (y correr el riesgo de encontrarse con errores sutiles), ¡o simplemente podría usar Lodash!

Nuestro primer ejemplo de matrices únicas es bastante trivial, pero aún representa la velocidad y la confiabilidad que Lodash aporta. ¡Imagínate hacer esto escribiendo tú mismo toda la lógica personalizada!

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 ]

Tenga en cuenta que la matriz final no está ordenada, lo que, por supuesto, no es motivo de preocupación aquí. Pero ahora, imaginemos un escenario más complicado: tenemos una matriz de usuarios que extrajimos de alguna parte, pero queremos asegurarnos de que solo contenga usuarios únicos. ¡Fácil con 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 }
]
*/

En este ejemplo, usamos el método uniqBy() para decirle a Lodash que queremos que los objetos sean únicos en la propiedad id. ¡En una línea, expresamos lo que podría haber tomado 10-20 líneas e introdujimos más posibilidades de errores!

Hay muchas más cosas disponibles para hacer que las cosas sean únicas en Lodash, y te animo a que eches un vistazo a la documentos.

Diferencia de dos arreglos

Unión, diferencia, etc., pueden sonar como términos que es mejor dejar atrás en las aburridas conferencias de la escuela secundaria sobre teoría de conjuntos, pero aparecen con mayor frecuencia en la práctica diaria. Es común tener una lista y querer fusionar otra lista con ella o querer encontrar qué elementos son únicos en comparación con otra lista; para estos escenarios, la función de diferencia es perfecta.

Hola, A. ¡Adiós, B!

Comencemos el viaje de la diferencia tomando un escenario simple: ha recibido una lista de todos los ID de usuario en el sistema, así como una lista de aquellos cuyas cuentas están activas. ¿Cómo encuentras las identificaciones inactivas? Sencillo, ¿verdad?

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 ]

¿Y si, como sucede en un entorno más realista, tiene que trabajar con una matriz de objetos en lugar de simples elementos primitivos? ¡Bueno, Lodash tiene un buen método differenceBy() para esto!

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' } ]

Genial, ¿verdad?

Al igual que la diferencia, existen otros métodos en Lodash para operaciones de conjuntos comunes: unión, intersección, etc.

Aplanamiento de matrices

La necesidad de aplanar matrices surge con bastante frecuencia. Un caso de uso es que recibió una respuesta de la API y necesita aplicar una combinación de map() y filter() en una lista compleja de objetos/matrices anidados para extraer, por ejemplo, ID de usuario, y ahora le queda matrices de matrices. Aquí hay un fragmento de código que representa esta situación:

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);

¿Puedes adivinar cómo se ven ahora los postPaidUserIds? Pista: ¡es repugnante!

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

Ahora, si es una persona sensata, no desea escribir una lógica personalizada para extraer los objetos de orden y colocarlos en una fila dentro de una matriz. Solo usa el método flatten() y disfruta de las 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' }
]
*/

Tenga en cuenta que flatten() solo tiene un nivel de profundidad. Es decir, si sus objetos están atascados a dos, tres o más niveles de profundidad, flatten() lo decepcionarán. En esos casos, Lodash tiene el método flattenDeep(), pero tenga en cuenta que aplicar este método en estructuras muy grandes puede ralentizar las cosas (ya que detrás de escena, hay una operación recursiva en funcionamiento).

¿Está vacío el objeto/matriz?

Gracias a la forma en que funcionan los valores y tipos «falsos» en JavaScript, a veces algo tan simple como verificar el vacío da como resultado un temor existencial.

¿Cómo se comprueba si una matriz está vacía? Puede comprobar si su longitud es 0 o no. Ahora, ¿cómo compruebas si un objeto está vacío? Bueno… ¡espera un momento! Aquí es donde se establece esa sensación incómoda, y esos ejemplos de JavaScript que contienen cosas como [] == false y {} == false empiezan a dar vueltas en nuestras cabezas. Cuando está bajo presión para ofrecer una función, las minas terrestres como estas son lo último que necesita: harán que su código sea difícil de entender e introducirán incertidumbre en su conjunto de pruebas.

Trabajar con datos faltantes

En el mundo real, los datos nos escuchan; no importa cuánto lo deseemos, rara vez es racionalizado y sensato. Un ejemplo típico es la falta de matrices/objetos nulos en una gran estructura de datos recibida como respuesta de la API.

Supongamos que recibimos el siguiente objeto como respuesta de la API:

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

Como se muestra, generalmente obtenemos un objeto de orden en la respuesta de la API, pero no siempre es así. Entonces, ¿qué pasa si tenemos algún código que se basa en este objeto? Una forma sería codificar a la defensiva, pero dependiendo de qué tan anidado esté el objeto de orden, pronto estaríamos escribiendo un código muy feo si deseamos evitar errores de tiempo de ejecución:

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
  );
}

🤢🤢 Sí, muy feo de escribir, muy feo de leer, muy feo de mantener, etc. Afortunadamente, Lodash tiene una forma sencilla de lidiar con tales situaciones.

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

También existe la fantástica opción de proporcionar un valor predeterminado en lugar de quedar indefinido por cosas que faltan:

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

No sé tú, pero get() es una de esas cosas que me hacen llorar de felicidad. No es nada llamativo. No hay sintaxis consultada ni opciones para memorizar, ¡pero mira la cantidad de sufrimiento colectivo que puede aliviar! 😇

antirrebote

En caso de que no esté familiarizado, la eliminación de rebotes es un tema común en el desarrollo de frontend. La idea es que a veces es beneficioso lanzar una acción no inmediatamente sino después de un tiempo (generalmente, unos pocos milisegundos). ¿Qué significa eso? Aquí hay un ejemplo.

Imagine un sitio web de comercio electrónico con una barra de búsqueda (bueno, ¡cualquier sitio web/aplicación web en estos días!). Para una mejor UX, no queremos que el usuario tenga que presionar enter (o peor, presione el botón «buscar») para mostrar sugerencias/vistas previas basadas en su término de búsqueda. Pero la respuesta obvia está un poco cargada: si agregamos un detector de eventos a onChange() para la barra de búsqueda y disparamos una llamada API para cada pulsación de tecla, habríamos creado una pesadilla para nuestro backend; habría demasiadas llamadas innecesarias (por ejemplo, si se busca «cepillo de alfombra blanca», ¡habrá un total de 18 solicitudes!) y casi todas serán irrelevantes porque la entrada del usuario no ha terminado.

La respuesta está en la eliminación de rebotes, y la idea es esta: no envíe una llamada a la API tan pronto como cambie el texto. Espere un tiempo (digamos, 200 milisegundos) y si en ese momento hay otra pulsación de tecla, cancele el conteo de tiempo anterior y comience a esperar nuevamente. Como resultado, solo cuando el usuario hace una pausa (ya sea porque está pensando o porque ha terminado y espera alguna respuesta) enviamos una solicitud de API al backend.

La estrategia general que describí es complicada, y no me sumergiré en la sincronización de la gestión y cancelación del temporizador; sin embargo, el proceso de eliminación de rebotes real es muy simple si está utilizando 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

Si estás pensando en setTimeout(), habría hecho el mismo trabajo, bueno, ¡hay más! El rebote de Lodash viene con muchas características poderosas; por ejemplo, es posible que desee asegurarse de que el rebote no sea indefinido. Es decir, incluso si hay una pulsación de tecla cada vez que la función está a punto de activarse (cancelando así el proceso general), es posible que desee asegurarse de que la llamada a la API se realice de todos modos después de, digamos, dos segundos. Para esto, Lodash debounce() tiene la opción maxWait:

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

Echa un vistazo al oficial documentos para una inmersión más profunda. ¡Están llenos de cosas súper importantes!

Eliminar valores de una matriz

No sé ustedes, pero odio escribir código para eliminar elementos de una matriz. Primero, tengo que obtener el índice del elemento, verificar si el índice es realmente válido y, de ser así, llamar al método splice(), y así sucesivamente. Nunca puedo recordar la sintaxis y, por lo tanto, necesito buscar cosas todo el tiempo, y al final, me quedo con la sensación persistente de que dejé que un error estúpido se colara.

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

Tenga en cuenta dos cosas:

  • La matriz original se cambió en el proceso.
  • El método pull() elimina todas las instancias, incluso si hay duplicados.
  • Hay otro método relacionado llamado pullAll() que acepta una matriz como segundo parámetro, lo que facilita la eliminación de varios elementos a la vez. De acuerdo, podríamos usar pull() con un operador de extensión, ¡pero recuerde que Lodash llegó en un momento en que el operador de extensión ni siquiera era una propuesta en el lenguaje!

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

    Último índice de un elemento

    El método indexOf() nativo de JavsScript es genial, ¡excepto cuando está interesado en escanear la matriz desde la dirección opuesta! Y una vez más, sí, podría escribir un ciclo decreciente y encontrar el elemento, pero ¿por qué no usar una técnica mucho más elegante?

    Aquí hay una solución rápida de Lodash usando el método lastIndexOf():

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

    Desafortunadamente, no existe una variante de este método en la que podamos buscar objetos complejos o incluso pasar una función de búsqueda personalizada.

    Cremallera. ¡Abrir la cremallera!

    A menos que haya trabajado en Python, comprimir/descomprimir es una utilidad que quizás nunca haya notado o imaginado en toda su carrera como desarrollador de JavaScript. Y quizás por una buena razón: rara vez existe el tipo de necesidad desesperada de comprimir/descomprimir como existe para filter(), etc. Sin embargo, es una de las mejores utilidades menos conocidas y puede ayudarlo a crear código sucinto en algunas situaciones. .

    Al contrario de lo que parece, comprimir/descomprimir no tiene nada que ver con la compresión. En cambio, es una operación de agrupación en la que las matrices de la misma longitud se pueden convertir en una única matriz de matrices con elementos en la misma posición empaquetados juntos (zip()) y viceversa (unzip()). Sí, lo sé, se está volviendo confuso tratar de arreglármelas con palabras, así que veamos un poco 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' ] ]

    Las tres matrices originales se convirtieron en una sola con solo dos matrices. Y cada una de estas nuevas matrices representa un solo animal con todo en un solo lugar. Entonces, el índice 0 nos dice que tipo de animal es, el índice 1 nos dice su tamaño y el índice 2 nos dice su peso. Como resultado, ahora es más fácil trabajar con los datos. Una vez que haya aplicado las operaciones que necesita en los datos, puede dividirlos nuevamente usando unzip() y enviarlos de regreso a la fuente original:

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

    La utilidad de comprimir/descomprimir no es algo que cambiará su vida de la noche a la mañana, ¡pero cambiará su vida algún día!

    Conclusión 👨‍🏫

    (Pongo todo el código fuente usado en este artículo aquí para que pruebes directamente desde el navegador!)

    El Lodash documentos están repletos de ejemplos y funciones que simplemente te dejarán boquiabierto. En una época en la que el masoquismo parece estar aumentando en el ecosistema JS, Lodash es como una bocanada de aire fresco, ¡y recomiendo encarecidamente que utilice esta biblioteca en sus proyectos!