Cómo usar el mapa para modificar dinámicamente una matriz en JavaScript

Cómo usar el método .map() en JavaScript para modificar dinámicamente una matriz de objetos.

Primeros pasos

Debido a que el código que estamos escribiendo para este tutorial es "independiente" (lo que significa que no es parte de una aplicación o proyecto más grande), vamos a crear un proyecto de Node.js desde cero. Si aún no tiene Node.js instalado en su computadora, lea este tutorial primero y luego regrese aquí.

Una vez que haya instalado Node.js en su computadora, desde la carpeta de proyectos en su computadora (por ejemplo, ~/projects ), crea una nueva carpeta para nuestro trabajo:

Terminal

mkdir map

A continuación, cd en ese directorio y crea un index.js archivo (aquí es donde escribiremos nuestro código para el tutorial):

Terminal

cd map && touch index.js

A continuación, en la misma carpeta, ejecute npm init -f para arrancar un package.json archivo:

Terminal

npm init -f

Esto le indicará a NPM (Node Package Manager) que cree un nuevo package.json archivo en su aplicación. El -f parte significa "fuerza" y omitirá el asistente paso a paso que ve cuando ejecuta npm init por sí mismo (siéntete libre de usar esto para entender cómo funciona).

Finalmente, necesitamos instalar dos dependencias:dayjs y currency.js .

Terminal

npm i dayjs currency.js

Usaremos estos dos para analizar y formatear nuestros datos como parte de nuestro .map() .

Con eso, estamos listos para comenzar.

Adición de datos de usuario

Nuestro objetivo para este tutorial es usar el Array.map() en JavaScript para dar formato a algunos datos de usuario y ayudarnos a comprender quiénes son nuestros usuarios más valiosos. Para comenzar, agreguemos algunos datos de prueba en un archivo separado en la raíz de nuestro proyecto:

/usuarios.js

export default [
  {
    "createdAt": "2021-12-08T16:20:14+00:00",
    "invoices": [
      {
        "createdAt": "2021-12-08T16:20:14+00:00",
        "amount": 790.31
      },
      {
        "createdAt": "2021-12-07T16:20:14+00:00",
        "amount": 893.38
      },
      {
        "createdAt": "2021-12-06T16:20:14+00:00",
        "amount": 302.97
      },
      ...
    ],
    "name": {
      "first": "Wester",
      "last": "Christian"
    },
    "emailAddress": "[email protected]"
  },
  ...
];

Nota:esta es una lista abreviada ya que la lista real (disponible aquí en Github) es bastante larga.

Una vez que tenga eso en la aplicación, estamos listos para pasar a escribir nuestro .map() sobre esta matriz.

Mapeo sobre la matriz de usuarios

Para empezar, construyamos un esqueleto para nuestro .map() función y revisar y discutir cómo va a funcionar:

/index.js

import users from './users.js';

const spendByUser = users.map((user) => {
  // We'll return our modified user here...
});

console.log(spendByUser);

De vuelta en nuestro /index.js archivo, aquí, importamos nuestro /users.js archivo como users (recuerde, tenemos un export default en ese archivo para que podamos decir import users en nuestro código:si se tratara de una exportación con nombre, veríamos algo como import { users } from '...' ).

Porque sabemos que users La variable debe contener una matriz (lo que exportamos de /users.js ), podemos llamar a .map() directamente sobre él. Esto se debe a que .map() es una función integrada en JavaScript. Está definido en el Array prototipo (el nombre utilizado para el objeto que contiene la funcionalidad heredada por una función en JavaScript). Decimos "A" mayúscula Array aquí porque esa es la función en JavaScript que define el comportamiento de una matriz. Como parte de su objeto prototipo, tenemos el .map() función (conocida como método porque es una función definida en un objeto existente).

Como sus hermanos, .map() nos permite realizar un bucle sobre una matriz y hacer algo . El algo en este caso es modificar elementos en un arreglo y devolverlos, creando un nuevo arreglo con los elementos modificados. Un ejemplo rápido:

const greetings = ['Hello', 'Goodbye', 'See ya'];

const greetingsWithName = greetings.map((greeting) => {
  return `${greeting}, Ryan!`
});

console.log(greetingsWithName);

// ['Hello, Ryan!', 'Goodbye, Ryan!', 'See ya, Ryan!']

Aquí tomamos una matriz de cadenas y usamos .map() para recorrer esa matriz. Para cada cadena en la matriz, devolvemos una nueva cadena (creada usando acentos graves para que podamos aprovechar la interpolación de cadenas de JavaScript). En respuesta a nuestra llamada a greetings.map() obtenemos una nueva matriz. Es importante entender :esta es una matriz nueva y única. El .map() la función crea una copia de cualquier matriz que llamemos .map() y devuelve esa nueva matriz.

Aquí, almacenamos eso en una variable greetingsWithName y luego console.log() para ver la copia modificada.

/index.js

import dayjs from 'dayjs';
import users from './users.js';

const spendByUser = users.map((user) => {
  return {
    isLegacyCustomer: dayjs(user?.createdAt).isAfter(dayjs().subtract(60, 'days')),
    name: `${user?.name?.first} ${user?.name?.last}`,
  };
});

console.log(spendByUser);

Ahora que entendemos los fundamentos de .map() , comencemos a modificar nuestro users formación. Los mismos principios exactos están en juego como vimos anteriormente:tomamos una matriz, llamamos .map() en él, y obtener una nueva matriz a cambio.

Arriba, observe que arriba de nuestro users import hemos importado una de las dependencias que instalamos anteriormente:dayjs . Abajo en nuestro .map() función, en la devolución de llamada pasamos a .map() , estamos devolviendo un objeto. Nuestro objetivo aquí es hacer un "análisis" de cada usuario y averiguar cuánto ha gastado cada cliente y si es o no un cliente heredado.

Aviso:no tenemos que devolver exactamente la misma forma de objeto (o incluso un objeto) desde nuestro .map() . Solo tenemos que devolver lo que queramos que tenga lugar en el elemento actual que estamos mapeando en la matriz.

Aquí, queremos crear un nuevo objeto que tenga tres propiedades:

  1. isLegacyCustomer que nos dice como booleano true o false independientemente de si el cliente se considera heredado o no.
  2. name que es la cadena de nombre completo del usuario/cliente.
  3. spend que es la cantidad de dinero que han gastado con nosotros, compuesta por un total de sus invoices matriz.

Aquí, nos estamos enfocando solo en isLegacyCustomer y name (spend es un poco más complicado, así que lo agregaremos a continuación).

Para isLegacyCustomer , queremos saber si el usuario se creó en nuestra base de datos hace más de 60 días (solo pretendemos aquí). Para averiguarlo, tomamos el createdAt propiedad en el user objeto y páselo a dayjs() (la función que importamos del paquete del mismo nombre arriba).

dayjs es una biblioteca para manipular y trabajar con fechas. Aquí, para facilitar nuestro trabajo, usamos dayjs() para decirnos si la marca de tiempo la pasamos (user.createdAt ) está después otra fecha que estamos creando sobre la marcha con otra llamada a dayjs :dayjs().subtract(60, 'days') . Aquí, obtenemos un dayjs objeto que contiene una fecha de 60 días antes de ahora.

En respuesta, esperamos el .isAfter() función en dayjs para devolvernos un booleano true o false .

Para el name campo, creamos una cadena usando el mismo patrón de acento grave que vimos anteriormente, esta vez usando interpolación para concatenar (unir) el nombre y apellido de nuestro usuario (usando el name.first y name.last propiedades del name objeto en el usuario).

/index.js

import dayjs from 'dayjs';
import currency from 'currency.js';
import users from './users.js';

const spendByUser = users.map((user) => {
  return {
    isLegacyCustomer: dayjs(user?.createdAt).isAfter(dayjs().subtract(60, 'days')),
    name: `${user?.name?.first} ${user?.name?.last}`,
    spend: user?.invoices?.reduce((total, invoice) => {
      total += invoice.amount;
      return currency(total, { precision: 2 }).value;
    }, 0),
  };
});

console.log(spendByUser);

Ahora para la parte difícil. Para obtener el spend propiedad para nuestros usuarios, necesitamos usar otro método de matriz .reduce() para recorrer el user.invoices formación. Similar a un .map() , el .reduce() El método recorre o itera a través de la matriz en la que se llama al método.

Sin embargo, en lugar de devolver una nueva matriz, un .reduce() El método devuelve cualquier valor que asignemos al acc o "acumulador". El acumulador en una función de reducción es un valor que comienza como algún valor y, si usamos .reduce() para el propósito previsto:devuelve una versión modificada o "actualizada" de ese valor.

Aquí, el acumulador comienza como 0 pasado como segundo argumento a user?.invoices?.reduce() (los signos de interrogación solo dicen "si el usuario existe y existen facturas en eso, llame a .reduce() en user.invoices "). Para cada ciclo o iteración de user.invoices , el valor actual del acumulador (nuevamente, comenzando como eso 0 ) se pasa como el primer argumento de la función que pasamos a .reduce() , aquí etiquetado como total . Como segundo argumento, obtenemos acceso al elemento actual en la matriz que se está repitiendo.

Si miramos nuestro código aquí, nuestro objetivo es "totalizar" el invoice.amount campo para cada objeto en el user.invoices matriz.

Para cada iteración de nuestro .reduce() , tomamos el valor actual de total y agregue el actual invoice.amount lo. A continuación, tomamos el total resultante y pásalo al currency() función que importamos de currency.js en la parte superior de nuestro archivo. Esto nos ayuda a formatear correctamente el valor de la moneda como un número flotante (por ejemplo, 123.45 ). A esa función, le pasamos total como primer argumento y luego un objeto de opciones para la función con precision: 2 como una propiedad, diciendo "formatear este número a dos lugares decimales".

Finalmente, devolvemos el .value propiedad en el objeto devuelto por la llamada a currency(total, { precision: 2 }) . Lo que return aquí se convierte en el valor nuevo o "actualizado" para el acumulador que estará disponible como total en el siguiente ciclo/iteración de user?.invoices . Así que está claro, total en nuestro código aquí obtendrá lo siguiente para cada iteración con esta matriz de ejemplo (suponiendo que comencemos en 0 ):

[{ amount: 1 }, { amount: 2.55 }, { amount: 3.50 }]

total = 0 // first item
total = 1
total = 3.55
total = 7.05 // last item

Una vez que nuestro .reduce() completa, esperamos recuperar el valor final de total (después de que se haya agregado el último elemento) a cambio. Esto significa que spend debe contener el total spend para cada uno de nuestros usuarios.

¡Eso es todo! Si le damos una vuelta a esto (asegurándonos de cerrar la sesión spendByUser en la parte inferior de nuestro archivo), deberíamos obtener algo como esto:

[
  { isLegacyCustomer: true, name: 'Wester Christian', spend: 10729.91 },
  { isLegacyCustomer: true, name: 'Carthon Weaver', spend: 14926.53 },
  { isLegacyCustomer: true, name: 'Keldrin Durham', spend: 13491.61 },
  { isLegacyCustomer: true, name: 'Jurgen Espinosa', spend: 13179.59 },
  ...
]

Para terminar, echemos un vistazo a cómo hacer uso de estos datos.

Clasificación basada en datos mapeados

Entonces, ¿por qué querríamos hacer algo como esto? Como la mayoría de las cosas, depende de nuestro código y del proyecto en el que estemos trabajando. Sin embargo, para agregar contexto, podríamos suponer que estamos tratando de encontrar un cliente para recompensar en función de su gasto total con nuestra empresa. Ahora que tenemos a mano nuestra matriz mapeada, podemos hacer algo como esto:

/index.js

import dayjs from 'dayjs';
import currency from 'currency.js';
import users from './users.js';

const spendByUser = users.map((user) => { ... });

const mostValuableCustomer = spendByUser.sort((a, b) => a.spend - b.spend).pop();
console.log({ mostValuableCustomer });

Aquí, hemos añadido dos líneas. Hemos creado una variable mostValueCustomer y para ello, estamos configurando el resultado de llamar al .sort() método (otro Array método prototipo) y pasándole una función. Esa función toma el elemento actual y el siguiente elemento de la matriz y los compara para encontrar cuál debe aparecer primero. Aquí, ordenamos por gasto total para decir "comenzar con el menor gasto en la parte superior y terminar con el mayor gasto en la parte inferior".

Con ese resultado (esperamos recuperar la copia ordenada de nuestro spendByUser matriz), llamamos al .pop() método para decir "quitar el último elemento de la matriz y devolverlo". Aquí, ese último artículo (el cliente con el mayor spend ) se almacena en el mostValuableCustomer variable. Si desconectamos esta variable, esto es lo que deberíamos obtener cuando ejecutamos nuestro código:

{ mostValuableCustomer: { isLegacyCustomer: false, name: 'Vicente Henry', spend: 15755.03 } }

Terminando

En este tutorial, aprendimos a usar el Array.map método para recorrer una matriz existente y modificar su contenido. También aprendimos a formatear datos como parte de ese .map() así como cómo usar los datos resultantes en nuestro código.