Skip to content
This page has been auto-translated and may contain errors.View in English

Memoización

El capítulo de renderizado es el modelo mental en el que se apoya este: cuando el estado cambia, React vuelve a ejecutar el componente y todo lo que está debajo, y eso normalmente está bien porque la mayoría de los renderizados son rápidos. A veces no lo es. Un cálculo al inicio de un componente puede tardar lo suficiente como para notarse, o un subárbol de componentes puede ser lo suficientemente costoso que re-renderizar todos ellos en cada pulsación hace que la aplicación se sienta lenta.

Para esos casos medidos, React ofrece tres herramientas: useMemo, memo y useCallback, y las tres hacen variaciones de una sola cosa: permiten que React recuerde un resultado del renderizado anterior e ignore el trabajo de repetirlo. Para usar cualquiera de ellas bien, primero necesitas saber cómo JavaScript decide si dos cosas son "lo mismo".

Tipos de valor, tipos de referencia e igualdad referencial

JavaScript divide los valores en dos familias. Los tipos de valor (también llamados primitivos) son booleanos, números, strings, y los que encuentras menos frecuentemente: undefined, null, symbol y bigint. Dos de ellos son iguales cuando tienen el mismo valor del mismo tipo: 1 === 1 es verdadero. Los tipos de referencia (también llamados tipos complejos) son objetos, arrays y funciones. Dos de ellos son iguales solo cuando son literalmente la misma cosa en memoria. Verse idénticos no cuenta para nada.

jsx
const a = { name: 'María' }
const b = { name: 'María' }

console.log(Object.is(a, b)) // false: dos objetos separados en memoria

const c = a
console.log(Object.is(a, c)) // true: ambos apuntan al mismo objeto

Object.is es lo que React usa internamente para estas comparaciones, y se comporta casi idénticamente a === aquí. Incluso {} !== {}: dos literales de objeto vacío son dos asignaciones distintas, así que fallan la comprobación. Esto se llama igualdad referencial, y es el concepto raíz de todo este capítulo.

Ahora conéctalo al renderizado. Un componente es una función, y cada renderizado vuelve a ejecutar su cuerpo de arriba hacia abajo. Cualquier objeto, array o función creado en ese cuerpo es completamente nuevo en cada renderizado:

jsx
function App() {
  const styles = { backgroundColor: 'black' } // objeto nuevo en cada renderizado
  function increment() { /* ... */ }          // función nueva en cada renderizado
  // ...
}

El contenido puede ser idéntico de un renderizado a otro, pero las referencias nunca lo son. Cualquier comparación que pregunte "¿cambió esta prop?" verificando Object.is responderá afirmativamente cada vez. Todo lo que sigue se trata de controlar cuándo cambian esas referencias.

useMemo: salta un cálculo costoso

El primer uso de memoización nada tiene que ver con referencias aún. Un cálculo en el nivel superior de un componente se ejecuta en cada renderizado, incluso los renderizados desencadenados por estado que el cálculo nunca toca. Si ordenar una lista grande de productos toma un tiempo notable, un botón de contador en otro lugar del mismo componente se vuelve entrecortado, porque cada clic vuelve a ejecutar la ordenación sin razón.

useMemo arregla esto con la misma forma que un efecto: una función más un array de dependencias. React llama a la función, recuerda qué devuelve, y en renderizados posteriores devuelve el valor recordado sin llamar a la función de nuevo, mientras nada en el array de dependencias haya cambiado.

jsx
import { useMemo, useState } from 'react'

function ProductList({ productsData }) {
  const [count, setCount] = useState(0)

  const sortedProducts = useMemo(() => {
    return [...productsData].sort((a, b) => a.name.localeCompare(b.name))
  }, [productsData])

  // ...
}

El array de dependencias contiene todo lo que el cálculo lee: aquí, solo productsData. Cambiar count re-renderiza el componente, pero React ve que productsData es lo mismo y salta la ordenación completamente. Solo un nuevo array productsData desencadena un recálculo. En la demostración del curso, eso redujo la ordenación de un lag visible en cada clic a cero milisegundos en renderizados que no tocaban los datos.

memo: salta el re-renderizado de un hijo

useMemo almacena en caché un valor dentro de un componente. memo opera un nivel arriba: puede saltar re-renderizar un componente entero. Es un componente de orden superior, una función que toma tu componente y devuelve una versión mejorada del mismo. A menudo lo verás escrito React.memo en codebases más antiguas; la importación moderna tiene nombre.

jsx
import { memo } from 'react'

function Product({ name, styles }) {
  // un cuerpo de componente costoso
}

export default memo(Product)

El componente mejorado compara sus props anteriores con sus props entrantes. Si cada prop es lo mismo, React salta re-renderizar para ese renderizado padre, y el subárbol que hubiera renderizado se salta con él (con excepciones, cubiertas abajo). En la aplicación de demostración del curso, envolver un componente en memo redujo un cambio de estado de treinta y uno a un renderizado, porque todo el subárbol no tocado se saltó en una sola decisión.

"Lo mismo" significa igualdad superficial: cada prop se compara con Object.is. Una prop string o número se compara por valor y se comporta como esperarías. Y es exactamente donde comienza el problema.

Por qué memo silenciosamente deja de funcionar

Pasa a un componente envuelto en memo una prop de objeto, y la optimización muere silenciosamente:

jsx
function App() {
  const [darkMode, setDarkMode] = useState(false)
  const [count, setCount] = useState(0)

  const styles = {
    backgroundColor: darkMode ? '#222' : '#fff',
    color: darkMode ? '#fff' : '#000',
  }

  return <Product styles={styles} /* ... */ />
}

Cada vez que count cambia, App re-renderiza y construye un objeto styles fresco. Los valores dentro son idénticos, pero la igualdad referencial no le importa: Object.is(oldStyles, newStyles) es falso, memo concluye que las props cambiaron, y Product de todas formas se re-renderiza. Nada produce error, nada advierte. La memoización no hace nada en absoluto.

Este es el segundo trabajo de useMemo: mantener una referencia entre renderizados para que una comparación pueda tener éxito. Envuelve la creación del objeto y dale la única dependencia con la que realmente varía:

jsx
const styles = useMemo(() => ({
  backgroundColor: darkMode ? '#222' : '#fff',
  color: darkMode ? '#fff' : '#000',
}), [darkMode])

Ahora styles es el mismo objeto, por referencia, en cada renderizado hasta que darkMode cambie. La comprobación de memo pasa, y Product se mantiene saltado cuando solo el contador se mueve. Las dos herramientas se componen: memo en el hijo pregunta "¿cambiaron las props?", y useMemo en el padre asegura que la respuesta pueda ser no.

children es una prop como cualquier otra, y esto atrapa a la gente. Los hijos JSX escritos en el padre son un objeto de elemento nuevo en cada renderizado padre, así que <Card><Chart /></Card> le pasa a Card una nueva referencia children cada vez y memo en Card nunca tiene éxito, sin importar cuán estables sean las otras props. El patrón estructural que renderizado describe funciona del otro lado: cuando el elemento se crea arriba del componente que posee el estado, ese re-renderizado del componente ve el mismo objeto elemento y salta la rama sin ninguna memoización.

Qué memo no salta

memo solo gobierna las props que llegan del padre. Un componente memoizado aún se re-renderiza cuando su propio estado cambia y cuando un contexto que lee se actualiza. El contexto llega más allá de un subárbol saltado también: un descendiente que consume un contexto cambiado se re-renderiza incluso aunque el wrapper memoizado arriba fue saltado.

useCallback: useMemo para funciones

Las funciones son tipos de referencia también, así que una prop de callback rompe memo exactamente de la misma forma. Define function increment() {...} en un cuerpo de componente, pásalo hacia abajo, y el hijo recibe una referencia de función completamente nueva en cada renderizado del padre. useCallback existe precisamente para esto: memoiza la función misma.

jsx
const [selectedProduct, setSelectedProduct] = useState(null)

const chooseProduct = useCallback((id) => {
  console.log('Nuevo producto seleccionado')
  setSelectedProduct(id)
}, [])

Pasa chooseProduct hacia abajo a un hijo envuelto en memo y la optimización se mantiene: useCallback(fn, deps) mantiene la misma referencia de función entre renderizados hasta que una dependencia cambie. La relación con useMemo es cercana: con useMemo el valor recordado es lo que tu función devuelve, mientras que con useCallback el valor recordado es la función que pasas.

Un detalle a llevar contigo: funciones de estado setter como setSelectedProduct ya tienen una identidad estable, React lo garantiza. Pasar un setter crudo hacia abajo como una prop es seguro sin useCallback; el hook gana su lugar cuando envuelves el setter en tu propia función que primero hace trabajo extra.

Una referencia de función estable también importa más allá de memo. Si una función aparece en un array de dependencias de useEffect, una referencia nueva en cada renderizado significa que el efecto se re-ejecuta en cada renderizado, como efectos cubre. useCallback también lo quieta.

Mide primero

Confirma que hay un problema de rendimiento antes de buscar cualquiera de esto. Renderizado hace el caso y expone el flujo de trabajo: la mayoría de los renderizados son baratos, un renderizado que no cambia nada nunca llega al DOM, y el Profiler con una CPU limitada es cómo encuentras los que sí cuestan algo. Memoizar un cálculo trivial como data.length cuesta más de lo que ahorra, porque el almacenamiento en caché y la comparación son en sí mismos trabajo. Arregla solo lo que puedes medir, y arregla renderizados lentos antes de gastar energía reduciendo el número de renderizados.

Nota de versión

El Compilador de React, que alcanzó 1.0 en octubre de 2025, automatiza la mayoría de lo que este capítulo cubre: analiza componentes en tiempo de construcción e inserta memoización automáticamente, así que useMemo, useCallback y memo escritos a mano se vuelven en gran medida innecesarios en proyectos que lo adoptan. Los conceptos siguen siendo valiosos de entender, tanto porque los codebases existentes están llenos de estos hooks como porque saber qué hace el compilador por ti hace su comportamiento legible en lugar de mágico.

Mecánicamente, useMemo almacena el valor devuelto y el array de dependencias en el slot del componente en el árbol interno de React. En el siguiente renderizado camina el nuevo array de dependencias contra el almacenado, comparando elemento por elemento con Object.is; cualquier desajuste re-ejecuta la función y reemplaza ambos. Eso hace que el array de dependencias mismo esté sujeto a igualdad referencial: una dependencia de objeto que obtiene una referencia nueva en cada renderizado derrota la memoización desde adentro, por eso la estabilización tiende a en cascada hacia arriba a través de un componente.

memo ejecuta la misma comparación Object.is a través de cada clave del objeto props. Acepta un segundo argumento opcional, una función que recibe las props viejas y nuevas para que definas igualdad tú mismo, y los docs de React tienen razón en que casi nunca lo necesitarás.

El borde más afilado es que la comparación es todo-o-nada por componente: una prop inestable entre diez estables re-renderiza el hijo cada vez, así que un wrapper memo es solo tan bueno como la prop menos estable que recibe, y un wrapper que nunca dispara se muestra en el Profiler como un subárbol que sigue re-renderizándose.

Ten dos límites en mente. useCallback(fn, deps) es literalmente useMemo(() => fn, deps), así que cualquier cosa verdadera de uno es verdadera del otro. Y useMemo es una pista de rendimiento en lugar de una garantía semántica: React se reserva el derecho de tirar valores en caché, por ejemplo cuando un componente suspende durante su montaje inicial, o en desarrollo cada vez que editas el archivo, así que el código debe mantenerse correcto si la función se re-ejecuta. Almacena cosas que deben sobrevivir re-renderizados sin excepción en estado o un ref. El mismo razonamiento de identidad estable se aplica a los valores de proveedores de contexto, que el deep dive de ese capítulo ya cubre.

JunoLas referencias estables saltan trabajo React vuelve a ejecutar todo el cuerpo de un componente en cada renderizado, así que cualquier cálculo lento en ahí se ejecuta una y otra vez. useMemo permite que React recuerde la respuesta y la reutilice hasta que las entradas cambien. memo hace algo similar para un componente entero: si sus props son lo mismo que la última vez, React salta renderizarlo.

La parte complicada es que dos objetos o funciones que se ven idénticos aún cuentan como diferentes en JavaScript, por eso estas herramientas a veces se necesitan entre sí. Y antes de usar cualquiera de ellas, asegúrate que algo es realmente lento.

JunoLas referencias estables saltan trabajo Envuelve un cálculo costoso en useMemo con sus entradas reales como dependencias, y envuelve un hijo costoso en memo para saltarlo cuando las props no han cambiado.

Luego recuerda que objetos y funciones creados en un cuerpo de componente son nuevas referencias en cada renderizado, así que un hijo memo recibiéndolas de todas formas se re-renderiza: estabiliza objetos con useMemo y funciones con useCallback. Los setters de estado ya son estables.

Mide con una CPU limitada primero; la mayoría de re-renderizados no cuesta nada que valga la pena optimizar.

JunoLas referencias estables saltan trabajo Todo aquí se reduce a Object.is: los arrays de dependencias se comparan elemento a elemento, memo compara props superficialmente, y una referencia inestable en cualquier lugar rompe la cadena. useCallback(fn, deps) es useMemo(() => fn, deps), y useMemo es una pista que React puede descartar, así que mantén la corrección independiente del caché.

El Compilador de React ahora automatiza este análisis en tiempo de construcción; los hooks manuales importan para código existente y para entender qué está haciendo el compilador en tu nombre.

Lo siguiente: Separación de código, donde la otra palanca, el tamaño del bundle, tiene su turno.