A tu propio ritmo

Explora nuestra extensa colección de cursos diseñados para ayudarte a dominar varios temas y habilidades. Ya seas un principiante o un aprendiz avanzado, aquí hay algo para todos.

Bootcamp

Aprende en vivo

Únete a nosotros en nuestros talleres gratuitos, webinars y otros eventos para aprender más sobre nuestros programas y comenzar tu camino para convertirte en desarrollador.

Próximos eventos en vivo

Catálogo de contenidos

Para los geeks autodidactas, este es nuestro extenso catálogo de contenido con todos los materiales y tutoriales que hemos desarrollado hasta el día de hoy.

Tiene sentido comenzar a aprender leyendo y viendo videos sobre los fundamentos y cómo funcionan las cosas.

Buscar en lecciones


← Regresar a lecciones

El Hook useMemo en React: Optimizando Cálculos Costosos

¿Qué es useMemo?
useMemo vs. Variables Regulares
  • Sin useMemo:

La optimización del rendimiento es una consideración importante en las aplicaciones React, especialmente cuando crecen en complejidad. El hook useMemo es una herramienta poderosa que te permite memorizar cálculos costosos para evitar recomputaciones innecesarias durante los renderizados.

¿Qué es useMemo?

useMemo es un hook de React que memoriza el resultado de una llamada a función. Solo recalcula el valor memorizado cuando una de sus dependencias ha cambiado, lo que puede mejorar significativamente el rendimiento al evitar cálculos costosos en cada renderizado.

1const valorMemorizado = useMemo(() => calcularValorCostoso(a, b), [a, b]);

En esta sintaxis:

  • El primer argumento es una función que realiza el cálculo que quieres memorizar
  • El segundo argumento es un array de dependencias (similar a useEffect)
  • El hook devuelve el valor memorizado, que cambia solo cuando las dependencias cambian

Cuándo Usar useMemo

Deberías considerar usar useMemo cuando:

  1. Tienes operaciones computacionalmente costosas
  2. El cálculo depende de props o estado que podrían no cambiar en cada renderizado
  3. Quieres evitar cálculos innecesarios que podrían afectar al rendimiento

Ejemplo Básico: Cálculo Costoso

1import React, { useState, useMemo } from 'react'; 2 3function CalculadoraFactorial() { 4 const [number, setNumber] = useState(1); 5 const [otherState, setOtherState] = useState(false); 6 7 // Este cálculo solo se ejecutará cuando 'number' cambie 8 const factorial = useMemo(() => { 9 console.log('Calculando factorial...'); 10 // Cálculo costoso 11 let result = 1; 12 for (let i = 1; i <= number; i++) { 13 result *= i; 14 } 15 return result; 16 }, [number]); // Solo recalcular cuando number cambia 17 18 return ( 19 <div> 20 <input 21 type="number" 22 value={number} 23 onChange={e => setNumber(parseInt(e.target.value))} 24 /> 25 <p>Factorial de {number} es: {factorial}</p> 26 <button onClick={() => setOtherState(!otherState)}> 27 Cambiar otro estado 28 </button> 29 </div> 30 ); 31}

En este ejemplo:

  • El cálculo del factorial es costoso para números grandes
  • Lo envolvemos en useMemo para evitar recalcularlo en cada renderizado
  • El cálculo solo se ejecuta cuando el estado number cambia
  • Cuando otherState cambia, el componente se vuelve a renderizar pero no recalcula el factorial

useMemo vs. Variables Regulares

Para entender el beneficio de useMemo, comparémoslo con un enfoque de variable regular:

Sin useMemo:

1function SinMemo({ list, filter }) { 2 // Esto se ejecuta en CADA renderizado 3 const filteredList = list.filter(item => item.includes(filter)); 4 5 return ( 6 <div> 7 <p>Encontrados {filteredList.length} elementos</p> 8 <ul> 9 {filteredList.map(item => <li key={item}>{item}</li>)} 10 </ul> 11 </div> 12 ); 13}

Con useMemo:

1function ConMemo({ list, filter }) { 2 // Esto solo se ejecuta cuando list o filter cambian 3 const filteredList = useMemo(() => { 4 return list.filter(item => item.includes(filter)); 5 }, [list, filter]); 6 7 return ( 8 <div> 9 <p>Encontrados {filteredList.length} elementos</p> 10 <ul> 11 {filteredList.map(item => <li key={item}>{item}</li>)} 12 </ul> 13 </div> 14 ); 15}

Casos de Uso Comunes para useMemo

1. Memorizar Listas Filtradas o Ordenadas

1function ListaDeProductos({ products, searchTerm, sortOrder }) { 2 const filteredAndSortedProducts = useMemo(() => { 3 // Primero filtrar los productos 4 const filtered = searchTerm 5 ? products.filter(product => 6 product.name.toLowerCase().includes(searchTerm.toLowerCase()) 7 ) 8 : products; 9 10 // Luego ordenarlos 11 return [...filtered].sort((a, b) => { 12 if (sortOrder === 'price') { 13 return a.price - b.price; 14 } else if (sortOrder === 'name') { 15 return a.name.localeCompare(b.name); 16 } 17 return 0; 18 }); 19 }, [products, searchTerm, sortOrder]); 20 21 return ( 22 <div> 23 {filteredAndSortedProducts.map(product => ( 24 <ProductItem key={product.id} product={product} /> 25 ))} 26 </div> 27 ); 28}

2. Prevenir la Creación Costosa de Objetos

1function ComponenteGrafico({ data, width, height }) { 2 // Memorizar objeto de opciones para prevenir renderizados innecesarios 3 const chartOptions = useMemo(() => { 4 return { 5 data, 6 dimensions: { 7 width, 8 height 9 }, 10 margin: { top: 20, right: 30, bottom: 40, left: 50 }, 11 animate: true, 12 colors: generateColorPalette(), // Función costosa 13 legend: buildLegendConfig() // Otra función costosa 14 }; 15 }, [data, width, height]); 16 17 return <Chart options={chartOptions} />; 18}

3. Memorizar Transformaciones en Props

1function TablaDeDatos({ rawData }) { 2 const processedData = useMemo(() => { 3 // Transformación compleja de datos 4 return rawData.map(item => ({ 5 ...item, 6 fullName: `${item.firstName} ${item.lastName}`, 7 formattedDate: formatDate(item.timestamp), 8 calculatedValue: calculateValue(item.values) 9 })); 10 }, [rawData]); 11 12 return ( 13 <table> 14 <thead> 15 <tr> 16 <th>Nombre</th> 17 <th>Fecha</th> 18 <th>Valor</th> 19 </tr> 20 </thead> 21 <tbody> 22 {processedData.map(item => ( 23 <tr key={item.id}> 24 <td>{item.fullName}</td> 25 <td>{item.formattedDate}</td> 26 <td>{item.calculatedValue}</td> 27 </tr> 28 ))} 29 </tbody> 30 </table> 31 ); 32}

useMemo y React.memo

useMemo funciona bien con React.memo para optimizar tanto el cálculo como el renderizado:

1// Componente hijo envuelto en React.memo 2const ComponenteCostoso = React.memo(({ data }) => { 3 // Renderizado costoso 4 return <div>{/* Renderizar datos */}</div>; 5}); 6 7function ComponentePadre() { 8 const [count, setCount] = useState(0); 9 const [items, setItems] = useState([1, 2, 3]); 10 11 // Este objeto está memorizado y solo se recrea cuando items cambia 12 const memoizedData = useMemo(() => { 13 return { 14 items, 15 meta: { 16 count: items.length, 17 modified: new Date() 18 } 19 }; 20 }, [items]); 21 22 return ( 23 <div> 24 <button onClick={() => setCount(count + 1)}> 25 Incrementar contador: {count} 26 </button> 27 28 {/* 29 Sin useMemo, se crearía un nuevo objeto data en cada renderizado, 30 causando que ComponenteCostoso se vuelva a renderizar incluso cuando items no ha cambiado 31 */} 32 <ComponenteCostoso data={memoizedData} /> 33 </div> 34 ); 35}

Consideraciones de Rendimiento

Aunque useMemo puede mejorar el rendimiento, también tiene costos:

  1. Sobrecarga de Memoria: La memorización almacena resultados previos, lo que usa memoria
  2. Cálculo Inicial: El primer cálculo sigue ocurriendo
  3. Verificación de Dependencias: React sigue verificando las dependencias en cada renderizado

Cuándo No Usar useMemo

No uses useMemo para cálculos simples donde la sobrecarga de memorización podría ser más costosa que simplemente recalcular el valor:

1// NO HAGAS ESTO - La sobrecarga no vale la pena 2const valorDuplicado = useMemo(() => valor * 2, [valor]); 3 4// HAZ ESTO EN SU LUGAR 5const valorDuplicado = valor * 2;

También evita usarlo para cada cálculo en tu componente por defecto. Solo añade memorización cuando hayas identificado un problema específico de rendimiento.

Mejores Prácticas

1. Medir Antes de Optimizar

Siempre mide el rendimiento antes y después de añadir useMemo para asegurarte de que realmente está ayudando:

1// Puedes usar console.time para medir 2function Componente({ data }) { 3 const processedData = useMemo(() => { 4 console.time('procesamiento de datos'); 5 const result = operacionCostosa(data); 6 console.timeEnd('procesamiento de datos'); 7 return result; 8 }, [data]); 9 10 // Resto del componente 11}

2. Usar las Dependencias Correctas

Al igual que con useEffect, incluye todos los valores del ámbito del componente que se utilizan dentro de la función de cálculo:

1function ResultadosBusqueda({ query, maxResults, data }) { 2 const results = useMemo(() => { 3 // Los tres valores se usan, así que los tres deberían ser dependencias 4 return data 5 .filter(item => item.name.includes(query)) 6 .slice(0, maxResults); 7 }, [data, query, maxResults]); // Todas las dependencias listadas 8 9 return <ListaResultados results={results} />; 10}

3. Memorizar Funciones en las Dependencias

Si tu cálculo memorizado depende de funciones, considera memorizar esas funciones con useCallback:

1function ListaFiltrable({ items }) { 2 const [filterText, setFilterText] = useState(''); 3 4 // Memorizar la función de filtro 5 const filterFunction = useCallback( 6 (item) => item.name.includes(filterText), 7 [filterText] 8 ); 9 10 // Usar la función memorizada en useMemo 11 const filteredItems = useMemo(() => { 12 return items.filter(filterFunction); 13 }, [items, filterFunction]); 14 15 // Resto del componente 16}

Ejemplo Complejo: Panel de Visualización de Datos

1import React, { useState, useMemo } from 'react'; 2import Chart from './Chart'; 3import DataTable from './DataTable'; 4 5function Dashboard({ rawData }) { 6 const [timeRange, setTimeRange] = useState('week'); 7 const [metric, setMetric] = useState('revenue'); 8 9 // Memorizar lógica de filtrado de tiempo 10 const timeFilteredData = useMemo(() => { 11 const now = new Date(); 12 const timeRanges = { 13 day: 24 * 60 * 60 * 1000, 14 week: 7 * 24 * 60 * 60 * 1000, 15 month: 30 * 24 * 60 * 60 * 1000 16 }; 17 18 const threshold = now.getTime() - timeRanges[timeRange]; 19 20 return rawData.filter(item => 21 new Date(item.timestamp).getTime() > threshold 22 ); 23 }, [rawData, timeRange]); 24 25 // Memorizar cálculos de métricas 26 const metricData = useMemo(() => { 27 // Agrupar por día 28 const grouped = timeFilteredData.reduce((acc, item) => { 29 const date = new Date(item.timestamp).toLocaleDateString(); 30 if (!acc[date]) { 31 acc[date] = { date, value: 0 }; 32 } 33 acc[date].value += item[metric]; 34 return acc; 35 }, {}); 36 37 // Convertir a array y ordenar 38 return Object.values(grouped).sort((a, b) => 39 new Date(a.date) - new Date(b.date) 40 ); 41 }, [timeFilteredData, metric]); 42 43 // Memorizar estadísticas 44 const stats = useMemo(() => { 45 if (metricData.length === 0) return { total: 0, avg: 0, max: 0 }; 46 47 const total = metricData.reduce((sum, item) => sum + item.value, 0); 48 return { 49 total, 50 avg: total / metricData.length, 51 max: Math.max(...metricData.map(item => item.value)) 52 }; 53 }, [metricData]); 54 55 return ( 56 <div className="dashboard"> 57 <div className="controls"> 58 <select 59 value={timeRange} 60 onChange={e => setTimeRange(e.target.value)} 61 > 62 <option value="day">Últimas 24 Horas</option> 63 <option value="week">Últimos 7 Días</option> 64 <option value="month">Últimos 30 Días</option> 65 </select> 66 67 <select 68 value={metric} 69 onChange={e => setMetric(e.target.value)} 70 > 71 <option value="revenue">Ingresos</option> 72 <option value="users">Usuarios</option> 73 <option value="pageviews">Vistas de página</option> 74 </select> 75 </div> 76 77 <div className="summary"> 78 <div className="stat"> 79 <h3>Total {metric}</h3> 80 <p>{stats.total.toLocaleString()}</p> 81 </div> 82 <div className="stat"> 83 <h3>Promedio {metric}</h3> 84 <p>{stats.avg.toLocaleString(undefined, { maximumFractionDigits: 2 })}</p> 85 </div> 86 <div className="stat"> 87 <h3>Máximo {metric}</h3> 88 <p>{stats.max.toLocaleString()}</p> 89 </div> 90 </div> 91 92 <Chart data={metricData} /> 93 94 <DataTable data={timeFilteredData} /> 95 </div> 96 ); 97}

Este ejemplo complejo demuestra varias capas de useMemo trabajando juntas:

  1. Primero, filtra datos en bruto basados en un rango de tiempo seleccionado
  2. Luego, transforma esos datos filtrados en un formato adecuado para gráficos
  3. Finalmente, calcula estadísticas resumidas de los datos transformados
  4. Cada cálculo solo se ejecuta cuando sus dependencias específicas cambian

Conclusión

El hook useMemo es una herramienta poderosa para optimizar el rendimiento de tus aplicaciones React al evitar recálculos innecesarios. Al memorizar cálculos costosos, puedes asegurarte de que solo se ejecuten cuando sus entradas realmente cambien, no en cada renderizado.

Puntos clave a recordar:

  • Usa useMemo para cálculos costosos que dependen de estado o props
  • Incluye todos los valores del ámbito del componente utilizados en el cálculo como dependencias
  • No abuses de useMemo para cálculos simples
  • Combina con React.memo para una optimización completa del renderizado
  • Siempre mide el rendimiento antes y después de optimizar

Recuerda que la optimización prematura puede llevar a código más complejo y difícil de mantener. Usa useMemo cuando hayas identificado un problema específico de rendimiento, no como un enfoque predeterminado para todos los cálculos en tus componentes.