Cómo usar Reducir al total una matriz de objetos en JavaScript

Cómo usar el método JavaScript Array.reduce para recorrer una matriz de elementos que representan un carrito, generando un objeto con un subtotal, un total de impuestos y un total (subtotal + impuestos).

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 reduce

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 reduce && touch index.js

Con eso, estamos listos para comenzar.

Crear una matriz de elementos

Lo primero que debemos hacer es crear una matriz de artículos que representarán nuestro carrito. Cada artículo en el carrito tendrá cuatro propiedades:

  1. id - Una identificación única para el elemento como un número entero.
  2. name - Un nombre para el elemento como una cadena.
  3. quantity - La cantidad de este artículo que se compra como un número entero.
  4. amount - La cantidad por artículo como un número flotante (decimal).

Agreguemos una matriz de ejemplo a nuestro archivo. Siéntase libre de jugar y cambiar esto como mejor le parezca (solo asegúrese de usar los mismos nombres clave en cada objeto y los tipos de datos correctos para los valores).

/index.js

const items = [
  { id: 1, name: 'Coffee Maker', quantity: 3, amount: 29.22 },
  { id: 2, name: 'Toaster Oven', quantity: 1, amount: 129.19 },
  { id: 3, name: 'Chef\'s Knife', quantity: 10, amount: 39.38 },
  { id: 4, name: 'Deep Fryer', quantity: 4, amount: 209.61 },
  { id: 5, name: 'Espresso Machine', quantity: 2, amount: 89.49 },
];

A continuación, antes de pasar a nuestra función de reducción, agreguemos otra variable taxRate a nuestro archivo que contendrá el porcentaje de impuestos que queremos calcular para cada artículo:

/index.js

const items = [
  { id: 1, name: 'Coffee Maker', quantity: 3, amount: 29.22 },
  { id: 2, name: 'Toaster Oven', quantity: 1, amount: 129.19 },
  { id: 3, name: 'Chef\'s Knife', quantity: 10, amount: 39.38 },
  { id: 4, name: 'Deep Fryer', quantity: 4, amount: 209.61 },
  { id: 5, name: 'Espresso Machine', quantity: 2, amount: 89.49 },
];

const taxRate = 0.0625; // 6.25%

Aquí, como implica el comentario, nuestra tasa impositiva será del 6,25%. Esto se expresa como un flotante 0.0625 .

Usando Array.reduce para generar un objeto

Ahora que tenemos algunos elementos con los que trabajar, estamos listos para poner .reduce() para trabajar en nuestra matriz. Analicemos primero nuestra llamada y hablemos sobre el objetivo final.

/index.js

const items = [
  ...
];

const taxRate = 0.0625; // 6.25%

const cart = items.reduce((acc = {}, item = {}) => {
  // We'll handle our calculations here...

  return acc;
}, {
  subtotal: 0,
  tax: 0,
  total: 0
});

console.log(cart);

Lo importante que hay que entender sobre .reduce() es que es un método de matriz estándar en JavaScript, como .forEach() o .map() . ¿Qué tiene de especial .reduce()? es que está diseñado para recorrer una matriz al igual que sus métodos hermanos, pero en lugar de simplemente recorrer una matriz (como .forEach() ) o recorrer una matriz y devolver una matriz modificada (como .map() ), está diseñado para "reducir una matriz a otra cosa".

En la cocina, el término "reducir" se usa para cocinar algo en otra forma (por ejemplo, derretir mantequilla junto con ajo para crear una salsa simple).

Aquí, queremos "reducir" nuestra matriz de elementos en un objeto que se vea así:

{
  subtotal: 0,
  tax: 0,
  total: 0,
}

La idea es que para cada iteración o "bucle" sobre nuestra matriz, agregamos valores a este objeto (subtotal , tax y total ), devolviendo ese objeto una vez que lleguemos al final de la matriz.

/index.js

const items = [
  ...
];

const taxRate = 0.0625; // 6.25%

const cart = items.reduce((acc = {}, item = {}) => {
  // We'll handle our calculations here...

  return acc;
}, {
  subtotal: 0,
  tax: 0,
  total: 0
});

console.log(cart);

Para manejar la parte de suma, al .reduce() método, pasamos una función que se llama para cada iteración o "bucle" sobre nuestra matriz de elementos. A esa función se le pasan dos argumentos:acc (abreviatura de acumulador) y item el elemento actual que se está reproduciendo.

Aquí, acc es el objeto que finalmente devolvemos de nuestra llamada a .reduce() (en este tutorial, los totales de nuestro carrito). Si miramos nuestra llamada a .reduce() aquí, notaremos que la función que acabamos de describir es el primer argumento que pasamos mientras que el valor inicial para el acumulador (acc ) se pasa como el segundo valor. Aunque estamos usando un objeto como valor inicial, técnicamente puede ser cualquiera Valor de JavaScript (por ejemplo, una cadena, un número entero u otra matriz).

Lo que podemos esperar aquí es que cuando nuestro .reduce() se ejecuta por primera vez (lo que significa que itera sobre el primer elemento de la matriz o, en nuestro ejemplo, el elemento "Cafetera" en el carrito), el valor de acc argumento pasado a la función pasada a .reduce() es:{ subtotal: 0, tax: 0, total: 0 } .

Nuestro objetivo es tomar ese acc y modifíquelo para cada iteración o "bucle" sobre nuestro items matriz, utilizando el valor actual de item para hacerlo.

/index.js

const items = [
  ...
];

const taxRate = 0.0625; // 6.25%

const cart = items.reduce((acc = {}, item = {}) => {
  const itemTotal = parseFloat((item.amount * item.quantity).toFixed(2));
  const itemTotalTax = parseFloat((itemTotal * taxRate).toFixed(2));

  // We'll modify acc here...

  return acc;
}, {
  subtotal: 0,
  tax: 0,
  total: 0
});

console.log(cart);

Antes de modificar nuestro acc (acumulador), necesitamos hacer algunas matemáticas y formatear nuestro artículo. Nuestro objetivo para cada artículo es generar dos totales:el total del artículo en sí (su amount multiplicado por su quantity ) y el monto del impuesto por ese artículo.

Para hacerlo, moviéndose de adentro hacia afuera, primero multiplicamos el item.amount valor con el item.quantity . Porque el resultado de eso podría producir un número decimal largo (por ejemplo, 191.07180001 ) envolvemos ese cálculo entre paréntesis y luego llamamos al .toFixed(2) método. Así que está claro, estamos haciendo esto:

(item.amount * item.quantity) // Produces a number which is captured by the parentheses.
(123.2910181).toFixed(2) // Converts the resulting number to a two place decimal number, formatted as a string.

El .toFixed(2) El método aquí dice "tome cualquier número que se haya producido a partir del cálculo y conviértalo a dos lugares decimales". Si el tercer dígito decimal es mayor o igual a 5, se redondeará hacia arriba (por ejemplo, 123.0157181 se redondeará a 123.02 ) mientras que un valor inferior a 5 se redondeará hacia abajo (por ejemplo, 123.014571811 se redondeará a 123.01 ) por .toFixed() .

Hay un pequeño problema que probablemente hayas adivinado:esto nos da nuestro número como una cadena, no como un flotante (disidentes de JavaScript 1, desarrolladores de JavaScript 0), lo que dificulta nuestra capacidad para realizar más cálculos.

Para evitar esto, envolvemos nuestro (item.amount * item.quantity).toFixed(2) cálculo con una llamada a parseFloat() que, como su nombre lo indica, convierte cualquier valor que le pasemos en un número flotante de JavaScript. Entonces, si pasamos algo como "123.02" , obtendremos un número flotante real 123.02 .

Para itemTotalTax , usamos exactamente el mismo enfoque, sin embargo, para este número multiplicamos el itemTotal acabamos de calcular con el taxRate variable que definimos anteriormente.

/index.js

const items = [
  { id: 1, name: 'Coffee Maker', quantity: 3, amount: 29.22 },
  { id: 2, name: 'Toaster Oven', quantity: 1, amount: 129.19 },
  { id: 3, name: 'Chef\'s Knife', quantity: 10, amount: 39.38 },
  { id: 4, name: 'Deep Fryer', quantity: 4, amount: 209.61 },
  { id: 5, name: 'Espresso Machine', quantity: 2, amount: 89.49 },
];

const taxRate = 0.0625; // 6.25%

const cart = items.reduce((acc = {}, item = {}) => {
  const itemTotal = parseFloat((item.amount * item.quantity).toFixed(2));
  const itemTotalTax = parseFloat((itemTotal * taxRate).toFixed(2));

  acc.subtotal = parseFloat((acc.subtotal + itemTotal).toFixed(2));
  acc.tax = parseFloat((acc.tax + itemTotalTax).toFixed(2));
  acc.total = parseFloat((acc.total + itemTotal + itemTotalTax).toFixed(2));

  return acc;
}, {
  subtotal: 0,
  tax: 0,
  total: 0
});

console.log(cart);

Ahora viene la parte divertida. Con nuestro itemTotal y itemTotalTax , estamos listos para modificar nuestro acc (acumulador). Recuerda:estamos cambiando acc para cada iteración o "bucle" sobre nuestro items matriz .

Para hacerlo, todo lo que tenemos que hacer es tomar el acc argumento pasado a nuestra función y modificarlo. Recuerda:técnicamente hablando acc puede contener cualquier valor; sin embargo, sabemos que contiene un objeto JavaScript debido al valor predeterminado que pasamos como segundo argumento a .reduce() .

Debido a esto, queremos modificar las propiedades individuales de ese objeto. Aquí, modificamos acc.subtotal , acc.tax y acc.total . Para cada uno, observe que establecemos el valor igual al actual valor de esa propiedad, más el total correspondiente que acabamos de calcular (ya sea el total del artículo o el total de impuestos del artículo).

Tenga en cuenta que para mantener nuestros números fijos en dos lugares decimales, usamos el .toFixed(2) combinado con parseFloat() truco para cada uno de los totales que establecemos en el objeto.

Aunque solo vemos el resultado final (esto se almacenará en el cart variable hemos asignado nuestra llamada a items.reduce() a), si nos desconectamos de cada iteración de nuestro ciclo, esperaríamos ver algo como esto:

{ subtotal: 0, tax: 0, total: 0 } // Initial value for acc we set as a default.
{ subtotal: 87.66, tax: 5.48, total: 93.14 }
{ subtotal: 216.85, tax: 13.55, total: 230.4 }
{ subtotal: 610.65, tax: 38.16, total: 648.81 }
{ subtotal: 1449.09, tax: 90.56, total: 1539.65 }
{ subtotal: 1628.07, tax: 101.75, total: 1729.82 }

La parte importante :observe que en la parte inferior de la función pasamos a .reduce() nos aseguramos de return el acc valor después lo hemos modificado. Esto es obligatorio . Así funciona el .reduce() actualiza el valor de acc después de que lo hayamos modificado.

¡Eso es todo! Ahora, para cada iteración de nuestro ciclo modificamos acc almacenando el resultado final en la variable cart . En la parte inferior de nuestro archivo, agreguemos un console.log(cart) para que podamos ver el resultado cuando ejecutamos nuestro código.

Para ejecutarlo, en una terminal desde la carpeta raíz de nuestro proyecto, si ejecutamos node index.js deberíamos ver algo como cerrar sesión:

{ subtotal: 1628.07, tax: 101.75, total: 1729.82 }

Terminando

En este tutorial, aprendimos a usar el Array.reduce() en JavaScript para convertir una matriz de objetos en un solo objeto. Para demostrar el uso, creamos un carrito ficticio de items y usó reduce para calcular el total de cada artículo junto con su tasa impositiva.