Saltar al contenido

useMemo y useCallback, sin cargo culto

¿Envolver todo en useCallback hace la app más rápida?

Al terminar vas a poder explicar

  • Explicar que useCallback es useMemo devolviendo una función
  • Reconocer cuándo memoizar no sirve absolutamente para nada
  • Justificar el costo de memoria y comparación que agrega cada memoización

Hay una costumbre que se propagó sola: envolver todo en useCallback por las dudas. Vamos a ver qué hacen realmente estos dos hooks, para poder decidir en vez de repetir.

Son el mismo hook

Literalmente. Esta es la relación entera:

1useCallback(fn, deps)  ≡  useMemo(() => fn, deps)

Los dos guardan [valor, deps] en el hook. Los dos comparan las deps con Object.is en cada render. Si son iguales, devuelven lo guardado; si no, recalculan.

La diferencia es qué se guarda: useMemo guarda el resultado de ejecutar la factory; useCallback guarda la función misma, sin ejecutarla.

Caché y recálculo, con las deps decidiendo

AGENDADOpaso 1 / 232
Traza
Cadena de hooks · <>

Sin hooks en este paso.

Árbol de fibers

  • <HostRoot>

Salida

Nada en el host todavía. React no commiteó ningún cambio.

createRoot().render()

Montaje inicial. No hay árbol previo, así que todo se va a crear desde cero.

VER EL CÓDIGO QUE SE ESTÁ TRAZANDO
1function ConMemo() {2  const [n, setN] = useState(0);3  const [otro, setOtro] = useState(0);4 5  const caro = useMemo(() => calculoPesado(n), [n]);6 7  return (8    <div>9      <button onClick={() => setN(v => v + 1)}>n = {n}</button>10      <button onClick={() => setOtro(v => v + 1)}>otro = {otro}</button>11      <p>calculado: {caro}</p>12    </div>13  );14}

Con el foco acá: ← → un paso · espacio reproduce · Inicio/Fin van a los extremos.

La primera interacción cambia un estado que no está en las deps: aparece useMemo() → caché y la factory no corre. La segunda cambia una dep y ahí sí: useMemo() → recalculado, con una referencia nueva.

Ninguno de los dos evita renders

Esta confusión es cara, así que vale la pena ser explícito:

  • useMemo evita recalcular un valor dentro de un render que igual va a ocurrir.
  • useCallback evita crear una referencia nueva, para que otro componente memoizado pueda hacer bailout.
  • memo es el único de los tres que puede evitar un render — y sólo del componente que envuelve.

useCallback sin un memo del otro lado, o sin ser dependencia de un efecto, no hace absolutamente nada útil. Sólo agrega un nodo a la cadena de hooks, un array que asignar y una comparación que correr, en cada render, para siempre.

Cuándo sí

useMemo vale la pena cuando alguna de estas dos es cierta:

  1. El cálculo es caro de verdad — ordenar, filtrar o transformar muchos elementos.
  2. El resultado se pasa como prop a un componente memoizado, o va en el array de deps de un efecto. Ahí lo que importa no es el costo sino la identidad.

useCallback vale la pena cuando la función se pasa a un hijo memoizado o es dependencia de un efecto. En cualquier otro caso, sacalo.

1// ✗ Overhead sin beneficio: el hijo no está memoizado.2const alClick = useCallback(() => setAbierto(true), []);3return <button onClick={alClick}>abrir</button>;4 5// ✓ Acá sí: sin esto, el memo de ListaPesada nunca funcionaría.6const alSeleccionar = useCallback((id) => setElegido(id), []);7return <ListaPesada items={items} onSelect={alSeleccionar} />;

Antes de seguir · predecí

¿Cuál de estos useMemo se justifica?

Lo que te llevás

useCallback es useMemo devolviendo una función. Ninguno evita renders: eso lo hace memo. Los dos cuestan memoria y comparaciones. Usalos cuando el cálculo es caro o cuando la identidad de la referencia importa río abajo — y en ningún otro caso.

Antes de marcarla, comprobá

  • ¿Podés explicar explicar que useCallback es useMemo devolviendo una función?
  • ¿Podés explicar reconocer cuándo memoizar no sirve absolutamente para nada?
  • ¿Podés explicar justificar el costo de memoria y comparación que agrega cada memoización?