memo, useMemo y useCallback en React | Nicolás Garzón
La memoización conserva un resultado entre renders mientras sus dependencias no cambien. Es una optimización, no una garantía semántica ni una herramienta para corregir lógica.
TypeScript
Copiar const ProductRow = memo ( function ProductRow ( { product } : Props) {
return < li> { product. name} < / li> ;
} ) ; React puede omitir el render si las props son iguales según comparación superficial.
TypeScript
Copiar const visibleProducts = useMemo (
( ) => filterProducts ( products, query) ,
[ products, query] ,
) ; Úsalo para cálculos realmente costosos o para estabilizar un valor necesario por otra optimización.
TypeScript
Copiar const handleDelete = useCallback ( ( id: string ) => {
setProducts ( ( current) => current. filter ( ( item) => item. id !== id) ) ;
} , [ ] ) ; Conserva la identidad de una función. No evita crear código interno ni hace más rápida cada llamada.
Componentes costosos que reciben props estables.
Cálculos medidos como lentos.
Dependencias que necesitan identidad estable.
Componentes pequeños.
Props que cambian en cada render de todos modos.
Cálculos triviales.
Código que todavía no fue medido.
Memoizar cada función por costumbre.
Olvidar dependencias.
Pasar objetos nuevos y esperar que memo funcione.
Usarlo para esconder renders causados por arquitectura deficiente.
Primero diseña correctamente, después mide y finalmente optimiza el cuello real.
Texto
Copiar memo → puede omitir render de un componente por props
useMemo → conserva el resultado de un cálculo
useCallback → conserva la referencia de una funciónNinguna evita automáticamente renders del padre, cambios de Context, state propio ni trabajo del navegador.
TypeScript
Copiar const ProductRow = memo ( function ProductRow ( { product } : Props) {
return < li> { product. name} < / li> ;
} ) ; Por defecto, React compara cada prop con Object.is. Si pasas un objeto nuevo:
TypeScript
Copiar < ProductRow product= { { id, name } } / > la referencia cambia y memo no puede omitir el render. Eso no convierte la creación de objetos en un error; solo indica que esta optimización no encuentra props estables.
TypeScript
Copiar memo ( Component, ( previous, next) => previous. id === next. id) Un comparador personalizado debe revisar todas las props que afectan la salida, incluidas funciones. Si declara igualdad incorrectamente, el componente puede leer closures antiguas. Además, una comparación profunda puede costar más que renderizar.
TypeScript
Copiar const total = useMemo ( ( ) => calculateTotal ( items) , [ items] ) ; El cálculo debe seguir siendo correcto sin la cache. No uses useMemo para:
Ejecutar efectos secundarios.
Garantizar que un objeto nunca cambie.
Conservar datos que el producto debe recordar.
Sincronizar con sistemas externos.
React puede descartar caches en escenarios específicos; la semántica pertenece a state, refs o fuentes externas.
TypeScript
Copiar const handleDelete = useCallback ( ( id: string ) => {
setItems ( ( current) => current. filter ( ( item) => item. id !== id) ) ;
} , [ ] ) ; El updater evita leer items, por lo que la función puede conservarse sin esa dependencia.
TypeScript
Copiar const handleSave = useCallback ( ( ) => save ( documentId) , [ documentId] ) ; Debe cambiar cuando cambia el documento. Una callback estable con dependencias incorrectas produce lógica obsoleta.
Se pasa a un hijo memoizado.
Es dependencia de otro Hook.
Se registra en una API que exige la misma referencia.
Forma parte de una API pública que documenta estabilidad.
Si el hijo no está memoizado y no existe otro contrato, useCallback suele añadir complejidad sin ahorrar trabajo.
TypeScript
Copiar const visibleProducts = useMemo (
( ) => filterProducts ( products, query) ,
[ products, query] ,
) ;
const handleSelect = useCallback ( ( id: string ) => {
setSelectedId ( id) ;
} , [ ] ) ;
return (
< MemoizedProductList
products= { visibleProducts}
onSelect= { handleSelect}
/ >
) ; Las tres piezas tienen sentido juntas si filterProducts o la lista son costosos y las props suelen permanecer iguales. Si cualquiera cambia siempre, la cadena puede no aportar.
Colocar state más cerca o pasar children puede evitar recalcular regiones sin APIs de memo:
TypeScript
Copiar function ColorPicker ( { children } : { children: React. ReactNode } ) {
const [ color, setColor] = useState ( "red" ) ;
return < section> { children} < / section> ;
} El padre puede crear children; actualizar state interno no obliga necesariamente a recrear ese elemento desde arriba.
Un componente memoizado vuelve a renderizar si consume un Context cuyo value cambió. Divide contextos o lee la parte necesaria en un componente exterior y pásala como prop estable. No esperes que memo bloquee dependencias reales.
Comparación de dependencias o props.
Memoria.
Más caminos mentales.
Riesgo de dependencias incorrectas.
APIs más difíciles de refactorizar.
TypeScript
Copiar const fullName = ` ${ firstName} ${ lastName} ` ; useMemo suele ser más costoso conceptualmente que recalcular.
Define una interacción lenta.
Usa React Profiler y navegador.
Identifica componente o cálculo dominante.
Aplica una optimización concreta.
Mide de nuevo en producción.
No optimices por cantidad de console.log; render no equivale a commit ni problema visible.
Con Compiler habilitado, React puede aplicar memoización automática a código compatible. Eso reduce usos manuales, pero no elimina:
Identidades requeridas por APIs externas.
Cálculos algorítmicamente caros.
Contexts amplios.
State mal colocado.
Necesidad de medir.
Confirma que el proyecto realmente utiliza Compiler antes de retirar contratos manuales.
Memoizar todo “por buenas prácticas”.
Usar useMemo para efectos.
Comparador que ignora callbacks.
Dependencias incompletas.
Objetos nuevos que rompen una cadena sin beneficio.
Optimizar componentes baratos mientras la red o layout domina.
Memoización es una optimización descartable, no una garantía de corrección.
memo, useMemo y useCallback resuelven niveles distintos.
Referencia estable solo importa bajo un contrato concreto.
Arquitectura y colocación de state suelen tener mayor impacto.
Mide antes y después.
¿Por qué memo no bloquea un cambio de Context?
¿Cuándo useCallback aporta al pasar una función?
¿Por qué un comparador profundo puede empeorar rendimiento?
¿Qué debe ocurrir si React descarta una cache de useMemo?
Ver respuestas
Porque Context es una dependencia distinta de las props.
Cuando el receptor evita trabajo gracias a una referencia estable o una API la exige.
Puede costar más que ejecutar el componente.
El resultado debe seguir siendo correcto al recalcularse.
React Compiler y memoización automática explica qué parte de este trabajo puede asumir el tooling.