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 / 232Sin 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:
useMemoevita recalcular un valor dentro de un render que igual va a ocurrir.useCallbackevita crear una referencia nueva, para que otro componente memoizado pueda hacer bailout.memoes 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:
- El cálculo es caro de verdad — ordenar, filtrar o transformar muchos elementos.
- 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?