React Compiler - Memoización automática que elimina useMemo, useCallback y React.memo manual
Receta
# Instala el compilador y su plugin de Babel
npm install -D babel-plugin-react-compiler
npm install -D react-compiler-healthcheck # opcional: verifica compatibilidad// babel.config.js
module.exports = {
plugins: [
["babel-plugin-react-compiler", {
// target por defecto es "19" para React 19
}],
],
};// next.config.js (Next.js 15+)
module.exports = {
experimental: {
reactCompiler: true,
},
};Cuándo usarlo: Habilita React Compiler en cualquier proyecto de React 19 para optimizar automáticamente los re-renderizados. Reemplaza la necesidad de useMemo, useCallback y React.memo en la gran mayoría de casos.
Ejemplo Funcional
// Antes: Memoización manual en todas partes
import { useMemo, useCallback, memo } from "react";
type Todo = { id: string; text: string; completed: boolean };
const TodoItem = memo(function TodoItem({
todo,
onToggle,
}: {
todo: Todo;
onToggle: (id: string) => void;
}) {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
{todo.text}
</li>
);
});
function TodoList({ todos }: { todos: Todo[] }) {
const [filter, setFilter] = useState<"all" | "active" | "completed">("all");
const filteredTodos = useMemo(() => {
switch (filter) {
case "active": return todos.filter((t) => !t.completed);
case "completed": return todos.filter((t) => t.completed);
default: return todos;
}
}, [todos, filter]);
const handleToggle = useCallback((id: string) => {
// lógica de alternar
}, []);
const stats = useMemo(() => ({
total: todos.length,
active: todos.filter((t) => !t.completed).length,
completed: todos.filter((t) => t.completed).length,
}), [todos]);
return (
<div>
<p>{stats.active} items left</p>
<ul>
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
))}
</ul>
</div>
);
}// Después: Con React Compiler -- no se necesita memoización manual
import { useState } from "react";
type Todo = { id: string; text: string; completed: boolean };
function TodoItem({
todo,
onToggle,
}: {
todo: Todo;
onToggle: (id: string) => void;
}) {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
{todo.text}
</li>
);
}
function TodoList({ todos }: { todos: Todo[] }) {
const [filter, setFilter] = useState<"all" | "active" | "completed">("all");
const filteredTodos = (() => {
switch (filter) {
case "active": return todos.filter((t) => !t.completed);
case "completed": return todos.filter((t) => t.completed);
default: return todos;
}
})();
function handleToggle(id: string) {
// lógica de alternar
}
const stats = {
total: todos.length,
active: todos.filter((t) => !t.completed).length,
completed: todos.filter((t) => t.completed).length,
};
return (
<div>
<p>{stats.active} items left</p>
<ul>
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
))}
</ul>
</div>
);
}Lo que esto demuestra:
- La versión "anterior" requiere
useMemo,useCallbackyReact.memopara evitar re-renderizados innecesarios - La versión "posterior" es código React simple y natural -- el compilador añade memoización en tiempo de compilación
- El código es más simple, más fácil de leer, e igualmente performante
Análisis Profundo
Cómo Funciona
- React Compiler es una herramienta de tiempo de compilación (plugin de Babel) que analiza tus componentes y hooks, e inserta automáticamente memoización.
- Entiende las reglas de hooks de React y las reglas de React (renderizado puro, props/state inmutables) para determinar qué valores pueden ser almacenados en caché de forma segura.
- El compilador transforma tu código para memoizar: elementos JSX, computaciones costosas, funciones callback y re-renderizados de componentes -- aproximadamente equivalente a envolver cosas en
useMemo,useCallbackyReact.memodonde sea apropiado. - Realiza memoización de grano fino -- puede memoizar subárboles JSX individuales dentro de un componente, no solo el valor completo devuelto.
- El compilador se enfoca en React 19 por defecto y usa la API de caché interna de React 19. Para React 17 y 18, establece
target: "17"otarget: "18"e instala el paquetereact-compiler-runtime. - Si el compilador detecta código que viola las reglas de React (por ejemplo, mutar props, efectos secundarios durante el renderizado), omite ese componente y lo deja sin optimizar. No rompe tu código.
- Puedes usar la directiva
"use no memo"en la parte superior de una función para rechazar un componente o hook específico de la optimización del compilador.
Variaciones
Despliegue gradual con modo opt-in:
// babel.config.js -- solo compila archivos con directiva "use memo"
module.exports = {
plugins: [
["babel-plugin-react-compiler", {
compilationMode: "annotation", // solo compila funciones con "use memo"
}],
],
};// Solo este componente es compilado
function OptimizedList({ items }: { items: string[] }) {
"use memo";
return (
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
);
}Rechazar un componente específico:
function LegacyComponent({ data }: { data: any }) {
"use no memo";
// Este componente no será optimizado por el compilador
// Útil para código que intencionalmente rompe las reglas de React
externalMutableStore.value = data;
return <div>{data.label}</div>;
}Verificación de salud antes de adoptar:
# Ejecuta la verificación de salud para ver qué tan compatible es tu base de código
npx react-compiler-healthcheck --verbosePlugin ESLint para validación de reglas:
npm install -D eslint-plugin-react-compiler// eslint.config.js
import reactCompiler from "eslint-plugin-react-compiler";
export default [
{
plugins: { "react-compiler": reactCompiler },
rules: {
"react-compiler/react-compiler": "error",
},
},
];Notas sobre TypeScript
- El compilador funciona con TypeScript de forma integrada -- se ejecuta después de la compilación de TypeScript (en el pipeline de Babel) o se integra con SWC en Next.js.
- No se necesitan cambios de tipo. Las transformaciones del compilador son transparentes para el sistema de tipos.
"use memo"y"use no memo"son directivas de literal de cadena (como"use strict") y no necesitan declaraciones de tipo.
Limitaciones
- Mutar objetos o arrays durante el renderizado -- El compilador asume renderizado puro. Mutar props, state o variables que se comparten entre renderizados causa almacenamiento en caché incorrecto. Solución: Sigue las reglas de React -- siempre crea nuevos objetos/arrays en lugar de mutar. El plugin ESLint captura la mayoría de violaciones.
- Efectos secundarios durante el renderizado -- Código como
console.log(Date.now())o modificar variables externas durante el renderizado puede producir resultados obsoletos cuando el compilador almacena en caché la salida. Solución: Mueve efectos secundarios auseEffecto manejadores de eventos. - Almacenes mutables externos -- Leer de variables globales mutables o almacenes que React no sigue (por ejemplo, un objeto global simple) puede romper bajo la optimización del compilador. Solución: Usa
useSyncExternalStorepara estado mutable externo. - useMemo/useCallback existentes no son perjudiciales -- Si ya tienes memoización manual, el compilador funciona con ella. No necesitas eliminarla antes de habilitar el compilador. Solución: Opcionalmente elimina la memoización manual para código más limpio, pero no es requerido.
- No todo el código se compila -- Los componentes que violan las reglas de React se omiten silenciosamente. Puedes no notar que un componente no está optimizado. Solución: Usa
eslint-plugin-react-compilerpara capturar problemas y verifica la salida de compilación del compilador para componentes omitidos. - Aumento del tiempo de compilación -- El compilador añade tiempo de análisis a tu compilación. En bases de código muy grandes esto puede ser notable. Solución: Usa la opción
compilationMode: "annotation"para compilar solo componentes opt-in durante la migración.
Alternativas
| Enfoque | Cuándo elegir |
|---|---|
| React Compiler | Predeterminado para proyectos de React 19 -- optimización automática, sin esfuerzo |
useMemo / useCallback manual | React 18 o cuando necesitas control explícito sobre qué se memoiza |
React.memo | React 18 o para componentes de clase que el compilador no maneja |
| Million.js | Compilador alternativo enfocado en optimización de diffing de DOM virtual |
| División manual de componentes | Aislando subárboles costosos cuando el compilador o memoización es insuficiente |
Directiva "use no memo" | Rechazando componentes específicos de la optimización del compilador |
Preguntas Frecuentes
¿Qué hace React Compiler en un nivel alto?
- Es un plugin de Babel en tiempo de compilación que analiza componentes y hooks, e inserta automáticamente memoización
- Reemplaza la necesidad de
useMemo,useCallbackyReact.memomanual en la mayoría de casos - Realiza memoización de grano fino -- puede memoizar subárboles JSX individuales, no solo el valor completo devuelto
¿Cómo habilito React Compiler en un proyecto Next.js?
// next.config.js
module.exports = {
experimental: {
reactCompiler: true,
},
};Para proyectos que no son Next.js, instala babel-plugin-react-compiler y añádelo a tu configuración de Babel.
¿Necesito eliminar las llamadas existentes a useMemo, useCallback y React.memo?
- No. El compilador funciona junto con la memoización manual existente
- Puedes opcionalmente eliminarlas para código más limpio, pero no es requerido
- El compilador no entra en conflicto ni hace doble memoización de estas llamadas
¿Qué sucede si un componente viola las reglas de React (por ejemplo, muta props)?
- El compilador omite silenciosamente ese componente y lo deja sin optimizar
- No rompe tu código -- el componente se renderiza como si el compilador no estuviera habilitado
- Usa
eslint-plugin-react-compilerpara detectar qué componentes se están omitiendo y por qué
¿Cómo rechazo un componente específico de la optimización del compilador?
function LegacyComponent({ data }) {
"use no memo";
// Este componente no será optimizado
externalStore.value = data;
return <div>{data.label}</div>;
}La directiva "use no memo" se coloca en la parte superior del cuerpo de la función.
¿Cómo despliego gradualmente el compilador con modo opt-in?
// babel.config.js
module.exports = {
plugins: [
["babel-plugin-react-compiler", {
compilationMode: "annotation",
}],
],
};Luego añade "use memo" en la parte superior de las funciones que quieras compilar. Solo se optimizan las funciones anotadas.
¿Cómo verifico si mi base de código es compatible con el compilador?
npx react-compiler-healthcheck --verboseEsto escanea tu base de código e reporta qué componentes siguen las reglas de React y cuáles pueden causar problemas.
¿Puede el compilador usarse con React 17 o 18?
- Sí. Establece
target: "17"otarget: "18"en la configuración del plugin - También debes instalar el paquete
react-compiler-runtime - El compilador se enfoca en React 19 por defecto
Limitación: ¿Por qué un console.log dentro de mi componente muestra valores obsoletos después de habilitar el compilador?
- El compilador almacena en caché la salida del renderizado cuando las entradas no han cambiado
- Efectos secundarios como
console.log(Date.now())durante el renderizado pueden producir resultados obsoletos - Mueve efectos secundarios a
useEffecto manejadores de eventos donde pertenecen
Limitación: Mi componente lee de una variable global mutable y devuelve datos obsoletos con el compilador. ¿Por qué?
- El compilador asume renderizado puro y no sigue almacenes mutables externos
- Leer de objetos globales simples que React no gestiona rompe bajo la optimización del compilador
- Usa
useSyncExternalStorepara estado mutable externo para que React pueda rastrear cambios
¿Afecta el compilador a los tipos de TypeScript o requiere cambios de tipo?
- No. Las transformaciones del compilador son transparentes para el sistema de tipos
- Se ejecuta después de la compilación de TypeScript (en el pipeline de Babel) o se integra con SWC en Next.js
"use memo"y"use no memo"son directivas de literal de cadena y no necesitan declaraciones de tipo
¿Aumenta el compilador de React los tiempos de compilación?
- Sí, añade tiempo de análisis a la compilación
- En bases de código muy grandes, esto puede ser notable
- Usa
compilationMode: "annotation"para compilar solo componentes opt-in durante una migración incremental
Relacionado
- Descripción General -- Lista completa de características de React 19
- Componentes de Servidor -- Los Componentes de Servidor no envían JS por lo que la memoización es menos relevante allí
- Ref como Prop -- Otra simplificación de API en React 19