Qué optimiza el compilador de React y qué deja en sus manos
Entienda qué tareas de rendimiento automatiza el Compilador de React, por qué las APIs lentas y los paquetes pesados siguen siendo su responsabilidad, y cómo adoptarlo de forma segura en una base de código React existente.
El compilador de React elimina gran parte de la memorización manual, lo que hace tentador considerarlo una solución para aplicaciones lentas. Pero no lo es: solo optimiza el trabajo de renderizado que React controla, y nada más. A continuación verá dónde se encuentra ese límite, qué ralentizaciones aún requieren la intervención de un ingeniero y cómo activar el compilador en una base de código existente sin causar regresiones.
De una lista de verificación para la memorización a la memorización automática
Durante mucho tiempo, los consejos sobre el rendimiento de React se reducían a una breve lista de herramientas:
useMemo()
useCallback()
React.memo()
Un componente se volvía a renderizar con demasiada frecuencia, así que lo envolvieron en una función de memoización. Un cálculo parecía costoso, por lo que se guardó en caché. Una función de callback se enviaba a un componente hijo, así que se estabilizó. Cada solución consistía en memorización manual, con un array de dependencias que había que mantener.
El compilador de React cambia ese valor por defecto. La versión 1.0 se considera estable, y la recomendación del equipo de React para el nuevo código es dejar que el compilador se encargue de la memorización en lugar de utilizar manualmente useMemo y useCallback. Consulte la documentación actual de React para obtener los últimos consejos sobre su adopción.
La clave se resume en una frase: el compilador puede hacer que el propio trabajo de React sea más eficiente, pero no tiene influencia sobre las tareas que ocurren fuera de React.
Qué hace realmente el compilador
En el momento de la compilación, el compilador analiza los componentes y los hooks e inserta mecanismos de memorización donde su análisis indica que es beneficioso. Tome como ejemplo un componente que filtra una lista de productos antes de renderizarla:
function ProductList({ products }) {
const visible = products.filter(
product => product.inStock
);
La matriz filtrada luego se envía directamente a un componente hijo:
return <List items={visible} />;
}
Antes de que existiera el compilador, un desarrollador preocupado por el rendimiento solía cachear explícitamente el filtro para que List recibiera siempre la misma referencia al array hasta que products cambiara:
const visible = useMemo(
() => products.filter(
product => product.inStock
),
[products]
);
Con el compilador activo, generalmente se omite ese código. Él mismo determina las dependencias, puede memorizarlas con mayor precisión que los hooks escritos a mano y no depende de que se indiquen correctamente las dependencias. React denomina a esto memorización automática para componentes y hooks.
No obstante, fíjese en el alcance mencionado en esa descripción: cada elemento de la lista es una construcción de React.
Un modelo mental: un optimizador de renderizado
Es útil imaginar al compilador como algo que se interponga entre dos tipos de tareas de React y las reduzca ambas:
React Compiler
│
┌───────────┴───────────┐
▼ ▼
Component work Hook calculations
│ │
▼ ▼
memoization memoization
│ │
└───────────┬───────────┘
▼
fewer unnecessary
React updates
Menos actualizaciones innecesarias pueden ser una verdadera ventaja. Sin embargo, la parte importante del diagrama es lo que omite.
Los servidores backend lentos están fuera de su alcance
Considere un componente asíncrono que espera a una API antes de renderizar un panel de control:
async function Dashboard() {
const data = await fetch(
"https://api.example.com/dashboard"
);
Una vez que llegan los datos, estos se pasan a la interfaz de usuario:
return <DashboardUI data={data} />;
}
Si ese endpoint tarda 800 ms, el compilador no logrará reducir ese tiempo a 50 ms. Solo puede simplificar el proceso de convertir datos en interfaz de usuario en React, pero no hará que su backend responda más rápido. Como línea de tiempo:
Browser
│
▼
React
│
▼
API request ──────────► 800ms
│
▼
Response
│
▼
React render
La memorización ocurre casi exclusivamente en el último paso. Si la mayor parte del tiempo se invierte en esperas de red, estás optimizando el nivel incorrecto; las soluciones son consultas más rápidas, caché, respuestas más pequeñas o carga paralela. Para separar el tiempo de red del tiempo del cliente, consulta Diagnóstico de problemas de rendimiento en React más allá del tiempo de respuesta de la API.
JavaScript que no deberías haber incluido
El mismo límite aplica al tamaño del paquete. Imagina una página de inicio que carga una biblioteca de gráficos pesada, un editor de texto enriquecido y un SDK de análisis antes incluso de que alguien abra las pantallas que los utilizan:
Initial page
│
├── React
├── UI
├── Chart library
├── Editor
└── Analytics SDK
El compilador no elimina ninguna dependencia; cada byte sigue siendo descargado, analizado y ejecutado. Lo que ayuda es el split de código, por ejemplo, cargando la pantalla de análisis según sea necesario:
const Analytics = lazy(
() => import("./Analytics")
);
Ahora el código se carga solo cuando la función se renderiza realmente, y un límite Suspense circundante muestra una solución temporal mientras se carga. Esta es una decisión de la arquitectura de paquetes, y ninguna cantidad de memorización puede sustituirla.
Dónde fallan las expectativas
Una situación común: una aplicación parece lenta, alguien activa el compilador, y el equipo espera este resultado:
Slow app
│
▼
React Compiler
│
▼
Fast app
Eso es demasiado simple. La lentitud tiene varias causas, y el compilador solo aborda uno de los aspectos:
SLOW APP
│
┌───────────────┼───────────────┐
▼ ▼ ▼
React work Network Browser
│ │ │
▼ ▼ ▼
Compiler API/cache JS/rendering
│ │ │
▼ ▼ ▼
Optimized Engineer Engineer
El compilador es una herramienta dentro del conjunto de herramientas para mejorar el rendimiento, no el conjunto completo en sí. La latencia de red y los costos del navegador, como la evaluación de scripts y el diseño de la interfaz, siguen requiriendo intervención humana.
¿Debería eliminar las llamadas existentes a useMemo?
No de forma automática. En código nuevo, la configuración predeterminada oficial es permitir que el compilador haga memorización. Para código existente, React recomienda mantener la memorización actual o eliminarla solo después de pruebas exhaustivas. La razón es de tipo comportamental, no solo relacionada con el rendimiento: un valor memorizado utilizado como dependencia de un Effect determina cuándo se vuelve a ejecutar ese Effect, por lo que cambiar la forma en que se memoriza puede afectar cuándo se activan los efectos secundarios.
Por lo tanto, resista la tentación de realizar una limpieza en toda la base de código de esta manera:
useMemo → DELETE
useCallback → DELETE
React.memo → DELETE
Un enfoque más seguro es gradual, con mediciones en cada paso:
Existing app
│
▼
Enable compiler
│
▼
Run tests
│
▼
Profile
│
▼
Remove unnecessary
manual memoization
carefully
El compilador existe para hacer que la memorización manual sea menos necesaria en el futuro, no para declarar erróneas todas las optimizaciones existentes de la noche a la mañana.
El compilador como herramienta de control de calidad del código
Una ventaja que recibe menos atención: el compilador incluye pasos de validación basados en las Reglas de React, y su integración con herramientas de detección de errores puede informar sobre patrones como estos:
set-state-in-render
set-state-in-effect
unsafe ref access
React señala que estos diagnósticos pueden revelar errores ya presentes en la aplicación. Dado que el compilador omite el código que viola sus suposiciones, las advertencias también indican qué componentes no están optimizados, lo que convierte esta función en parte del ciclo de retroalimentación diaria y no solo en un complemento para mejorar el rendimiento.
Analice primero, luego elija la solución
Cuando una interfaz parezca lenta, mida su rendimiento antes de utilizar cualquier herramienta, y deje que las mediciones determinen el problema:
SLOW UI
│
▼
PROFILE
│
┌────────┼────────┐
▼ ▼ ▼
React Network Browser
work │ │
│ │ │
▼ ▼ ▼
Compiler API/cache JS/render
│
▼
Targeted fix
Luego aplique la solución según lo que muestre el analizador de rendimiento:
- Recargues innecesarios de React: aquí es donde el Compilador de React puede ser útil.
- Una API lenta: mejore el endpoint o la estrategia de obtención de datos.
Puntos clave
El modelo anterior requería indicarle a React qué memorizar; el nuevo permite escribir código React correcto y confiar en el compilador para gran parte del trabajo de renderizado. Esto eleva la importancia de una habilidad: saber dónde se gasta realmente el tiempo.
- El compilador optimiza aquello que comprende, es decir, el renderizado de React y los cálculos relacionados con los hooks.
- No puede reducir el tamaño de una respuesta API excesiva, acelerar una consulta a la base de datos, eliminar un script de terceros ni comprimir una imagen de 5 MB.
- Tampoco puede salvar una arquitectura que genera trabajo innecesario en todas partes.
- En el código existente, háblelo activo, pruébelo, realice un análisis de rendimiento y solo entonces elimine la memorización manual.
- Trate sus diagnósticos como informes de errores gratuitos sobre violaciones de las Reglas de React.
No pregunte si el compilador está activo, sino dónde pasa tiempo la aplicación; la parte correspondiente a React es precisamente para lo que sirve el compilador.
Lecturas relacionadas
- Más allá del tamaño del bundle: encontrar lo que realmente hace lenta su aplicación web — Por qué reducir kilobytes rara vez soluciona un problema de lentitud y cómo rastrear el tiempo real de espera en servidores, procesos en cascada, carga dinámica, scripts y imágenes de terceros.