Kodokon kodokon.com

Memoización: memo, useMemo, useCallback

Aprende cuándo React.memo, useMemo y useCallback aceleran de verdad tu app, y cuándo la complican para nada.

10 min · 3 preguntas

Abrir esta lección en Kodokon

Cuando un componente se vuelve a renderizar, todos sus hijos se vuelven a renderizar por defecto, hayan cambiado sus props o no. No es una tragedia: un render solo toca el DOM si la salida cambia. La memoización se vuelve relevante cuando renders frecuentes se encuentran con subárboles costosos. Tres herramientas, tres objetivos: React.memo omite el render de un componente si sus props son superficialmente iguales, useMemo cachea un valor calculado, useCallback cachea una función. Las tres se basan en la igualdad de referencia.

JSX
import { memo } from "react";

const Row = memo(function Row({ item, onSelect }) {
  return (
    <li onClick={() => onSelect(item.id)}>{item.label}</li>
  );
});
Row solo se vuelve a renderizar si item u onSelect cambian de referencia.

La cadena es tan fuerte como su eslabón más débil. Si una sola prop recibe una referencia nueva en cada render - una función flecha en línea, un objeto o array literal - la comparación de memo falla y el componente se vuelve a renderizar de todos modos. useCallback estabiliza la referencia de la función para que la comparación tenga éxito.

JSX
import { useCallback } from "react";

function List({ items }) {
  const handleSelect = useCallback((id) => {
    console.log("selected:", id);
  }, []);

  return (
    <ul>
      {items.map((item) => (
        <Row
          key={item.id}
          item={item}
          onSelect={handleSelect}
        />
      ))}
    </ul>
  );
}
handleSelect conserva la misma referencia en cada render.

useMemo tiene dos usos legítimos: evitar un cálculo genuinamente costoso (ordenar o agregar miles de elementos), y estabilizar una referencia consumida por un hijo memoizado o listada en las dependencias de un efecto. Para un cálculo trivial, la caché cuesta más de lo que ahorra: comparación de dependencias, memoria retenida, lectura más pesada.

JSX
import { useMemo } from "react";

function Stats({ orders }) {
  const total = useMemo(() => {
    return orders.reduce(
      (sum, order) => sum + order.amount,
      0
    );
  }, [orders]);

  return <p>Total: {total} €</p>;
}
El total solo se recalcula si orders cambia de referencia.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. Un hijo está envuelto en React.memo, pero su padre le pasa una función flecha en línea como prop. ¿Resultado?
    • La memoización funciona: las funciones se comparan por su código fuente
    • La memoización es inútil: la función en línea crea una referencia nueva en cada render y la comparación falla
    • React lanza una advertencia en desarrollo
  2. ¿Qué garantiza useCallback(fn, [a]) mientras a no cambie?
    • Que la función nunca se recrea en memoria
    • Que la función solo se ejecutará una vez
    • Que se devuelve la misma referencia de función de un render al siguiente
  3. ¿En qué caso está claramente justificado useMemo?
    • Un cálculo costoso, o una referencia que estabilizar para un hijo memoizado o una dependencia de efecto
    • Cualquier valor derivado, por precaución
    • Cachear datos entre dos montajes del componente