Un componente de React se vuelve a renderizar cuando cambia su propio estado, cuando su padre se vuelve a renderizar o cuando cambia un contexto que lee. Renderizar significa que React vuelve a llamar a tu función de componente para obtener JSX nuevo; luego actualiza solo las partes de la página que de verdad cambiaron.
Abre la consola bajo la vista previa y haz clic en el botón. Los tres componentes registran, incluido Title, que no recibe ninguna prop. Renderizó porque lo hizo su padre.
Qué provoca un renderizado
React renderiza un componente exactamente por estas razones:
- El primer renderizado. La app arranca, o el componente aparece por primera vez en el árbol.
- Su estado cambió. Llamaste a un setter de
useStateo a un dispatch deuseReducercon un valor nuevo. - Su padre renderizó. Por defecto, cuando un componente renderiza, todos los componentes que devuelve también renderizan, hasta abajo.
- Un contexto que lee cambió. Un componente que llama a
useContext(SomeContext)renderiza cuando el proveedor más cercano pasa unvaluenuevo (consulta useContext).
Una creencia habitual es que un componente renderiza "porque cambiaron sus props". Eso invierte la causa. Las props son argumentos que el padre pasa mientras renderiza, así que las props nuevas solo pueden llegar cuando el padre renderiza. Y que el padre renderice basta por sí solo: el Title de arriba no tiene props y aun así renderiza cada vez.
Definir el estado con el valor que ya tiene (comparado con Object.is) no inicia un renderizado de los hijos. React puede igual llamar una vez a ese componente antes de notar que nada cambió, pero descarta el resultado.
Render y commit
Cada actualización pasa por dos fases:
- Render. React llama a tus componentes. Devuelven JSX, que son solo objetos que describen lo que debe mostrar la pantalla. Todavía no cambia nada en la página, y por eso el renderizado debe ser puro: nada de escribir en el DOM, nada de peticiones, nada de cambiar variables fuera del componente.
- Commit. React compara el resultado nuevo con el anterior y aplica las diferencias al DOM: inserta, quita o actualiza solo los nodos que cambiaron. Luego el navegador pinta, y después React ejecuta tus efectos.
El ejemplo de abajo renderiza en cada clic, pero React conserva el mismo elemento <input>. Un efecto se ejecuta después de cada commit y lo comprueba.
Escribe algo en el input y haz clic varias veces. El texto que escribiste se queda, porque React nunca reemplazó el input: el único cambio en el DOM por clic es el número dentro del <p>. El registro también muestra el orden, primero render y el efecto después del commit.
El DOM virtual, sin rodeos
"DOM virtual" es el nombre popular de los objetos que devuelven tus componentes. <p>Items: {count}</p> se compila a una llamada que crea un objeto como { type: 'p', props: { children: ['Items: ', 1] } }. Después de un renderizado, React recorre el árbol nuevo de estos objetos junto al anterior, y donde el tipo y la posición coinciden conserva el nodo del DOM existente y actualiza solo los atributos y el texto que cambiaron. Esta comparación se llama reconciliación.
El término es impreciso por dos razones. React no guarda una segunda copia del DOM ni compara dos DOMs: compara objetos de elementos con su propio árbol interno de componentes (el árbol de fibers). Y el mismo proceso funciona en destinos que no tienen DOM, como React Native. La documentación de React casi no usa la expresión y habla en cambio de renderizar y hacer commit. Lo que importa en la práctica es la consecuencia: renderizar es barato comparado con el trabajo en el DOM, porque la mayoría de los renderizados terminan en una actualización pequeña del DOM o en ninguna.
De cómo funciona la comparación se siguen dos reglas. Un tipo de elemento distinto en el mismo lugar (un <div> reemplazado por una <section>, o ComponentA por ComponentB) destruye el subárbol viejo y su estado. Y en las listas, key le dice a React qué elemento es cuál, para que pueda mover nodos en lugar de reconstruirlos.
Evitar los renderizados que no necesitas
La mayoría de los renderizados extra no tienen un costo que puedas notar. Cuando uno sí duele, por ejemplo una lista grande que se renderiza con cada pulsación en otra parte de la página, prueba esto en orden.
Baja el estado
Si solo una parte pequeña de la pantalla usa una pieza de estado, guarda ese estado en un componente que envuelva solo esa parte. Esta versión vuelve a renderizar ProductList en cada pulsación:
export default function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ProductList />
</>
);
}
Mueve el input y su estado a su propio componente y la lista ya no está dentro del componente que renderiza:
Escribe algunas letras: solo registra SearchBox.
Pasa children en su lugar
A veces el estado tiene que vivir en un envoltorio alrededor de la parte costosa, como un panel que alterna un resaltado. Haz que el envoltorio acepte children. El padre crea los elementos hijos, y como el padre no renderiza cuando cambia el estado del envoltorio, esos elementos son los mismos objetos de antes, así que React se los salta.
Haz clic en el botón de resaltado y solo registra Highlighter. Ahora mueve <Article /> dentro del JSX de Highlighter en lugar de {children}, y cada clic también registra render Article.
Memoiza
Cuando ninguna de las reorganizaciones encaja, envuelve el hijo en memo. React compara entonces sus props con las anteriores y se salta el renderizado cuando son todas iguales. Los objetos y funciones creados durante el renderizado son nuevos cada vez, así que memo suele venir con useMemo o useCallback para esas props. La página de React.memo lo muestra con ejemplos en funcionamiento.
import { memo } from 'react';
const ProductList = memo(function ProductList({ category }) {
// skipped while category stays the same
});
El React Compiler puede agregar este tipo de memoización por ti al hacer el build, pero reorganizar el estado sigue siendo lo primero que conviene probar: elimina el trabajo en lugar de guardarlo en caché.
Los renderizados suelen estar bien
Un renderizado es una llamada a función que devuelve objetos. React ejecuta miles de ellos rápido, y un renderizado que produce el mismo resultado no cambia el DOM. No agregues memo en todas partes "por si acaso": cada comparación tiene su propio costo y hace el código más difícil de leer. Mide primero. El Profiler de React DevTools muestra qué componentes renderizaron, por qué y cuánto tardó cada uno.
Preguntas frecuentes
¿Qué provoca que un componente de React se vuelva a renderizar?
Tres cosas: cambia su propio estado, su padre renderiza, o un contexto que lee con useContext recibe un valor nuevo. Las props no son un disparador aparte: las props nuevas solo llegan porque el padre renderizó.
¿Un hijo se vuelve a renderizar cuando el padre se vuelve a renderizar?
Sí, por defecto cada componente dentro de un padre que renderiza también renderiza, aunque sus props no hayan cambiado. Envolver el hijo en memo permite que React se lo salte cuando sus props son las mismas que la última vez.
¿Qué es el DOM virtual en React?
Es un nombre impreciso para el árbol de objetos simples de JavaScript (elementos de React) que devuelven tus componentes. React compara el árbol nuevo con el anterior y cambia solo los nodos reales del DOM que son distintos.
¿Volver a renderizar es malo para el rendimiento?
Normalmente no. Un renderizado es una llamada a función que produce objetos, y React solo toca el DOM donde el resultado cambió. Optimiza cuando un renderizado sea lento de forma medible, por ejemplo con el Profiler de React DevTools.
¿Cuál es la diferencia entre render y commit?
Renderizar es React llamando a tus componentes para saber cómo debe verse la pantalla. Hacer commit es React aplicando las diferencias al DOM. Un renderizado que produce el mismo resultado no aplica ningún cambio al DOM.