DEV Community

Cover image for 🔹`useMemo` y `useCallback` – Optimización de rendimiento (6/8)
Pedro Alvarado
Pedro Alvarado

Posted on

🔹`useMemo` y `useCallback` – Optimización de rendimiento (6/8)

📋 Tabla de contenidos

  1. useState – El fundamento del estado
  2. useEffect – Efectos secundarios y ciclo de vida
  3. useContext – Compartir estado sin props
  4. useReducer – Lógica de Estado Compleja y predecible
  5. useRef – Referencias, Almacenamiento y Escape
  6. useMemo y useCallback – Optimizando el Rendimiento
  7. Custom Hooks – Creando Lógica Reutilizable
  8. Errores Comunes y Soluciones

El problema: re-renderizados innecesarios y la igualdad referencial

Antes de entender la solución, es crucial entender el problema. En JavaScript, los objetos y las funciones son iguales por referencia, no por valor.

// A pesar de tener el mismo "valor", son dos objetos diferentes en memoria.
{} === {} // false

// Lo mismo ocurre con las funciones.
(() => {}) === (() => {}) // false
Enter fullscreen mode Exit fullscreen mode

En React, cada vez que un componente se re-renderiza, las funciones y objetos declarados dentro de él se crean de nuevo. Esto significa que sus referencias cambian en cada render.

¿Por qué es un problema? Si pasas una de estas funciones u objetos como prop a un componente hijo optimizado con React.memo, el hijo se re-renderizará de todas formas, porque desde su perspectiva, la prop (onSomething) ha cambiado.

Aquí es donde entran useMemo y useCallback. Son herramientas para decir a React: "No vuelvas a crear esto a menos que sus dependencias cambien".


El problema: re-renderizados innecesarios y la igualdad referencial

Antes de entender la solución, es crucial entender el problema. En JavaScript, los objetos y las funciones son iguales por referencia, no por valor.

// A pesar de tener el mismo "valor", son dos objetos diferentes en memoria.
{} === {} // false

// Lo mismo ocurre con las funciones.
(() => {}) === (() => {}) // false
Enter fullscreen mode Exit fullscreen mode

En React, cada vez que un componente se re-renderiza, las funciones y objetos declarados dentro de él se crean de nuevo. Esto significa que sus referencias cambian en cada render.

¿Por qué es un problema? Si pasas una de estas funciones u objetos como prop a un componente hijo optimizado con React.memo, el hijo se re-renderizará de todas formas, porque desde su perspectiva, la prop (onSomething) ha cambiado.

Aquí es donde entran useMemo y useCallback. Son herramientas para decir a React: "No vuelvas a crear esto a menos que sus dependencias cambien".


useMemo: Memorizando valores calculados

useMemo "memoriza" (cachea) el resultado de un cálculo costoso. La función que le pasas solo se volverá a ejecutar si una de las dependencias ha cambiado.

Úsalo para:

  • Cálculos computacionalmente caros (ej. filtrar, ordenar o transformar grandes listas).
  • Evitar que se creen nuevos objetos/arrays en cada render si se pasan como props a componentes optimizados.

Sintaxis:

const valorMemorizado = useMemo(() => calcularValorCostoso(a, b), [a, b]);
Enter fullscreen mode Exit fullscreen mode

useCallback: Memorizando funciones

useCallback es muy similar, pero en lugar de memorizar el resultado de una función, memoriza la propia definición de la función.

Úsalo para:

  • Evitar re-renderizados en componentes hijos optimizados (React.memo) que reciben funciones como props.
  • Como dependencia estable en otros hooks como useEffect.

Sintaxis:

const funcionMemorizada = useCallback(() => {
  hacerAlgo(a, b);
}, [a, b]);
Enter fullscreen mode Exit fullscreen mode

Es conceptualmente equivalente a: useMemo(() => () => { hacerAlgo(a, b); }, [a, b]).


Ejemplo práctico detallado: lista filtrable optimizada

Imagina una lista de usuarios que puedes filtrar. El filtrado puede ser lento si la lista es grande. Además, cada item de la lista tiene un botón para eliminarlo.

Componente UserItem (Optimizado)

import React from 'react';

// React.memo evita que este componente se re-renderice si sus props no cambian.
const UserItem = React.memo(({ user, onDelete }) => {
  console.log(`Renderizando UserItem para: ${user.name}`);
  return (
    <li>
      {user.name}
      <button onClick={() => onDelete(user.id)}>Eliminar</button>
    </li>
  );
});
Enter fullscreen mode Exit fullscreen mode

Componente Principal UserList

import React, { useState, useMemo, useCallback } from 'react';

const initialUsers = [
  { id: 1, name: 'Ana' },
  { id: 2, name: 'Carlos' },
  { id: 3, name: 'Beatriz' },
  { id: 4, name: 'Daniel' },
];

function UserList() {
  const [users, setUsers] = useState(initialUsers);
  const [filter, setFilter] = useState('');
  const [theme, setTheme] = useState('light'); // Estado extra para forzar re-renders

  // 1. Optimizando el cálculo del filtrado con `useMemo`
  // Esta función solo se re-ejecutará si `users` o `filter` cambian,
  // no cuando `theme` cambie.
  const filteredUsers = useMemo(() => {
    console.log('Filtrando usuarios...');
    return users.filter(user =>
      user.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [users, filter]);

  // 2. Optimizando la función de borrado con `useCallback`
  // `handleDelete` mantiene la misma referencia mientras `users` no cambie.
  // Esto evita que todos los `UserItem` se re-rendericen cuando cambiamos el tema o el filtro.
  const handleDelete = useCallback((userId) => {
    setUsers(prevUsers => prevUsers.filter(user => user.id !== userId));
  }, []); // Dependencia vacía porque usamos la actualización funcional de `setUsers`

  return (
    <div style={{ background: theme === 'light' ? 'white' : 'black', color: theme === 'light' ? 'black' : 'white' }}>
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        Cambiar Tema
      </button>
      <hr />
      <input
        type="text"
        placeholder="Filtrar usuarios..."
        value={filter}
        onChange={e => setFilter(e.target.value)}
      />
      <ul>
        {filteredUsers.map(user => (
          <UserItem key={user.id} user={user} onDelete={handleDelete} />
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

✅ Buenas practicas y patrones comunes

  1. NO optimices prematuramente: useMemo y useCallback tienen un coste. Usarlos en todos lados puede hacer tu aplicación más lenta. Aplícalos solo cuando hayas identificado un problema de rendimiento real (puedes usar el Profiler de React DevTools).
  2. React.memo es tu mejor amigo: useCallback por sí solo no hace nada si el componente que recibe la función no está envuelto en React.memo (o PureComponent).
  3. Dependencias, dependencias, dependencias: El array de dependencias es la clave. Asegúrate de incluir todos los valores del scope del componente que se usan dentro de la función memorizada. El linter de React te ayudará con esto.

🚨 Errores comunes y cómo evitarlos

  • Error: Usar useMemo para memorizar componentes.
    • Solución: Para memorizar un componente, envuélvelo en React.memo. useMemo es para valores, no para JSX.
  • Error: Olvidar el array de dependencias.
    • Solución: Si omites el array, la función se recalculará en cada render, haciendo la optimización inútil. Si quieres que se calcule solo una vez, usa [].
  • Error: Pasar una función creada en el cuerpo del componente a las dependencias de useEffect.

    // MAL ❌
    function MiComponente() {
      const fetchData = () => { /* ... */ };
      useEffect(() => {
        fetchData();
      }, [fetchData]); // `fetchData` es nueva en cada render -> bucle infinito
    }
    
    • Solución: Define la función dentro del useEffect si solo se usa ahí, o envuélvela en useCallback si se usa en varios sitios.
    // BIEN ✅
    const fetchData = useCallback(() => { /* ... */ }, []);
    useEffect(() => {
      fetchData();
    }, [fetchData]);
    

🚀 Retos prácticos

  1. Cálculo de Factorial: Crea un componente con un input numérico. Calcula y muestra el factorial de ese número. El cálculo debe estar memorizado con useMemo. Añade otro estado (como un toggle de tema) para verificar que el factorial no se recalcula innecesariamente.
  2. Lista de Hijos con React.memo: Crea un componente padre que renderiza una lista de 100 componentes hijos. Pasa una función onPress al hijo. Comprueba con un console.log cuántos hijos se re-renderizan cuando el padre cambia su estado. Luego, optimiza con useCallback y React.memo y comprueba la diferencia.
  3. Gráfico SVG Complejo: Simula un componente que renderiza un gráfico SVG (que puede ser costoso). Las coordenadas del gráfico se calculan a partir de un array de datos. Usa useMemo para memorizar las coordenadas calculadas y evitar re-calcularlas si otras props del componente cambian.

Top comments (0)