Compilador de React (React Forget) — Memorización automática en tiempo de compilación
Receta
// Paso 1: Instala el compilador
// npm install -D babel-plugin-react-compiler
// Paso 2: Añade a tu configuración de Next.js (next.config.ts)
const nextConfig = {
experimental: {
reactCompiler: true,
},
};
export default nextConfig;
// Paso 3: Elimina la memorización manual — el compilador se encarga
// ANTES: Memorización manual
function ProductList({ products, category }: Props) {
const filtered = useMemo(
() => products.filter((p) => p.category === category),
[products, category]
);
const handleClick = useCallback((id: string) => {
selectProduct(id);
}, [selectProduct]);
return <MemoizedGrid items={filtered} onClick={handleClick} />;
}
const MemoizedGrid = memo(Grid);
// DESPUÉS: El compilador auto-memoriza — código más limpio, mismo rendimiento
function ProductList({ products, category }: Props) {
const filtered = products.filter((p) => p.category === category);
const handleClick = (id: string) => selectProduct(id);
return <Grid items={filtered} onClick={handleClick} />;
}Cuándo usarlo: Al iniciar un nuevo proyecto React 19+ o migrar uno existente. El compilador elimina el código boilerplate de useMemo, useCallback y memo manteniendo un rendimiento equivalente o mejor.
Ejemplo Funcional
// ---- ANTES: Memorización manual con 15 líneas de boilerplate ----
import { memo, useMemo, useCallback, useState } from "react";
interface User {
id: string;
name: string;
role: "admin" | "user";
lastActive: Date;
}
function UserDashboard({ users }: { users: User[] }) {
const [search, setSearch] = useState("");
const [roleFilter, setRoleFilter] = useState<"all" | "admin" | "user">("all");
const [selectedId, setSelectedId] = useState<string | null>(null);
const filteredUsers = useMemo(
() =>
users
.filter((u) => roleFilter === "all" || u.role === roleFilter)
.filter((u) => u.name.toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => b.lastActive.getTime() - a.lastActive.getTime()),
[users, roleFilter, search]
);
const handleSelect = useCallback((id: string) => {
setSelectedId(id);
}, []);
const stats = useMemo(
() => ({
total: users.length,
admins: users.filter((u) => u.role === "admin").length,
active: users.filter((u) => {
const hourAgo = Date.now() - 3600_000;
return u.lastActive.getTime() > hourAgo;
}).length,
}),
[users]
);
return (
<div>
<MemoizedStatsBar stats={stats} />
<input value={search} onChange={(e) => setSearch(e.target.value)} />
<select
value={roleFilter}
onChange={(e) => setRoleFilter(e.target.value as "all" | "admin" | "user")}
>
<option value="all">Todos</option>
<option value="admin">Admin</option>
<option value="user">Usuario</option>
</select>
<MemoizedUserList
users={filteredUsers}
selectedId={selectedId}
onSelect={handleSelect}
/>
</div>
);
}
const MemoizedStatsBar = memo(function StatsBar({
stats,
}: {
stats: { total: number; admins: number; active: number };
}) {
return (
<div className="flex gap-4">
<span>Total: {stats.total}</span>
<span>Admins: {stats.admins}</span>
<span>Activos: {stats.active}</span>
</div>
);
});
const MemoizedUserList = memo(function UserList({
users,
selectedId,
onSelect,
}: {
users: User[];
selectedId: string | null;
onSelect: (id: string) => void;
}) {
return (
<ul>
{users.map((user) => (
<li
key={user.id}
onClick={() => onSelect(user.id)}
className={user.id === selectedId ? "bg-blue-100" : ""}
>
{user.name} ({user.role})
</li>
))}
</ul>
);
});
// ---- DESPUÉS: Compilador de React — mismo rendimiento, cero boilerplate ----
function UserDashboard({ users }: { users: User[] }) {
const [search, setSearch] = useState("");
const [roleFilter, setRoleFilter] = useState<"all" | "admin" | "user">("all");
const [selectedId, setSelectedId] = useState<string | null>(null);
const filteredUsers = users
.filter((u) => roleFilter === "all" || u.role === roleFilter)
.filter((u) => u.name.toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => b.lastActive.getTime() - a.lastActive.getTime());
const stats = {
total: users.length,
admins: users.filter((u) => u.role === "admin").length,
active: users.filter((u) => {
const hourAgo = Date.now() - 3600_000;
return u.lastActive.getTime() > hourAgo;
}).length,
};
return (
<div>
<StatsBar stats={stats} />
<input value={search} onChange={(e) => setSearch(e.target.value)} />
<select
value={roleFilter}
onChange={(e) => setRoleFilter(e.target.value as "all" | "admin" | "user")}
>
<option value="all">Todos</option>
<option value="admin">Admin</option>
<option value="user">Usuario</option>
</select>
<UserList
users={filteredUsers}
selectedId={selectedId}
onSelect={(id: string) => setSelectedId(id)}
/>
</div>
);
}
function StatsBar({ stats }: { stats: { total: number; admins: number; active: number } }) {
return (
<div className="flex gap-4">
<span>Total: {stats.total}</span>
<span>Admins: {stats.admins}</span>
<span>Activos: {stats.active}</span>
</div>
);
}
function UserList({
users,
selectedId,
onSelect,
}: {
users: User[];
selectedId: string | null;
onSelect: (id: string) => void;
}) {
return (
<ul>
{users.map((user) => (
<li
key={user.id}
onClick={() => onSelect(user.id)}
className={user.id === selectedId ? "bg-blue-100" : ""}
>
{user.name} ({user.role})
</li>
))}
</ul>
);
}Lo que esto demuestra:
- La versión optimizada por el compilador elimina 6 llamadas a hooks (
useMemox2,useCallbackx1, wrappers dememox2) y 15 líneas de boilerplate - El rendimiento es equivalente porque el compilador inserta memorización a nivel AST durante la compilación
- El código es más legible y mantenible sin arrays de dependencias manuales
- Sin cambios de comportamiento — el compilador solo añade optimizaciones, nunca cambia la semántica
Inmersión Profunda
Cómo Funciona
- Transformación en tiempo de compilación — El Compilador de React es un plugin de Babel que analiza tu código de componentes en tiempo de compilación. Identifica valores, funciones y elementos JSX que pueden ser cacheados de forma segura entre renderizados.
- Rastro automático de dependencias — El compilador analiza estáticamente qué variables depende cada expresión, luego envuelve esas expresiones en memorización con los arrays de dependencias correctos. Esto es más confiable que escribir manualmente arrays de dependencias.
- Memorización a nivel de componente — El compilador envuelve los valores de retorno del componente para que si las props y el state no han cambiado, el componente se salte el renderizado completamente. Esto reemplaza
React.memo. - Caché de grano fino — A diferencia de
useMemomanual que cachea con la granularidad que especifiques, el compilador puede cachear elementos JSX individuales dentro de un componente, potencialmente logrando una granularidad mejor que la optimización escrita a mano. - Cumplimiento de las Reglas de React — El compilador asume que tu código sigue las Reglas de React (renderizado puro, sin mutación durante el renderizado, orden de hooks estable). El código que viola estas reglas puede producir salida incorrecta cuando se compila.
Variaciones
Exclusión con la directiva "use no memo":
// Excluye un componente específico de la optimización del compilador
function DebugPanel({ data }: { data: unknown }) {
"use no memo";
// Este componente siempre se re-renderiza — útil para depuración
console.log("DebugPanel rendered at", Date.now());
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}Inclusión por archivo (migración gradual):
// next.config.ts — solo compila directorios específicos
const nextConfig = {
experimental: {
reactCompiler: {
compilationMode: "annotation",
// O apunta a rutas específicas:
// include: ["src/components/**"],
},
},
};// Incluye a nivel de componente con "use memo"
function ExpensiveComponent({ data }: Props) {
"use memo";
// El compilador optimiza este componente
return <Chart data={data} />;
}Verificar salida del compilador:
# Ve qué transforma el compilador
npx react-compiler-healthcheck
# O inspecciona la salida compilada
npx babel --plugins babel-plugin-react-compiler src/Component.tsxNotas sobre TypeScript
- El compilador funciona con TypeScript de forma inmediata. No se necesita configuración de tipo adicional.
- Las anotaciones de tipo se preservan — el compilador solo transforma el comportamiento en tiempo de ejecución.
- Los componentes genéricos son compatibles. El compilador los maneja igual que los componentes no genéricos.
- La directiva
"use no memo"es un literal de cadena y no necesita una declaración de tipo.
Trampas
-
El estado mutable externo rompe los supuestos del compilador — El compilador asume renderizado puro. Si un componente lee de una variable global mutable o una ref durante el renderizado, el compilador puede cachear un resultado obsoleto. Solución: Usa
useSyncExternalStorepara tiendas externas, o añade la directiva"use no memo". -
Efectos secundarios durante el renderizado — El código que registra, muta objetos externos o dispara solicitudes de red durante el renderizado será cacheado por el compilador, lo que significa que los efectos secundarios pueden no ejecutarse en cada renderizado como se esperaba. Solución: Mueve efectos secundarios dentro de
useEffecto manejadores de eventos. -
Computaciones no idempotentes — Si una computación de valor se basa en
Date.now(),Math.random()u otras fuentes no deterministas, el compilador puede cachear un resultado obsoleto. Solución: Mueve valores no deterministas dentro del state o effects. -
Los componentes de clase no se optimizan — El compilador solo funciona con componentes de función y hooks. Los componentes de clase se pasan sin cambios. Solución: Migra componentes de clase a componentes de función.
-
La memorización manual sigue siendo necesaria en casos raros — El compilador puede no optimizar límites entre componentes en todos los casos, particularmente con componentes de orden superior o render props. Solución: Perfila después de activar el compilador. Añade
memomanual solo donde la perfilación muestre que el compilador perdió una optimización. -
Mantén la memorización manual existente durante la migración — Eliminar
useMemo/useCallbackantes de activar el compilador deja tu aplicación sin optimizar. Solución: Activa el compilador primero, verifica que el rendimiento se mantenga, luego elimina la memorización manual.
Alternativas
| Enfoque | Compensación |
|---|---|
useMemo + useCallback + memo manual | Control total; detallado; arrays de dependencias propensos a errores |
| Compilador de React | Cero boilerplate; requiere React 19+; experimental |
| Colocación de state | Evita la necesidad de memorización en absoluto; puede requerir reestructuración |
| Selectores de Zustand | Suscripciones de grano fino; añade dependencia externa |
| Million.js | Compilador alternativo para optimización de DOM virtual; de terceros |
| Signals (Preact, Solid) | Modelo reactivo evita el problema de re-renderizado; framework diferente |
FAQs
¿Qué es el Compilador de React y cómo difiere de la memorización manual?
El Compilador de React es un plugin de Babel que analiza el código del componente en tiempo de compilación e inserta automáticamente memorización (equivalente a useMemo, useCallback y memo). A diferencia de la memorización manual, no escribes arrays de dependencias — el compilador rastrea dependencias estáticamente.
¿Cómo activas el Compilador de React en un proyecto Next.js?
// next.config.ts
const nextConfig = {
experimental: {
reactCompiler: true,
},
};
export default nextConfig;También instala el plugin de Babel: npm install -D babel-plugin-react-compiler.
¿Puedes excluir un componente específico de la optimización del compilador?
Sí. Añade la directiva "use no memo" en la parte superior del cuerpo del componente:
function DebugPanel({ data }: { data: unknown }) {
"use no memo";
console.log("Rendered at", Date.now());
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}¿Cuáles son las Reglas de React que el compilador asume que sigues?
- Renderizado puro: sin efectos secundarios durante el renderizado
- Sin mutación de props o state durante el renderizado
- Orden de hooks estable (sin hooks dentro de condiciones o bucles)
- Salida de renderizado idempotente para las mismas entradas
Violar estas reglas puede producir salida cacheada incorrecta.
Trampa: ¿Qué sucede si un componente lee una variable global mutable durante el renderizado?
El compilador puede cachear un resultado obsoleto porque asume renderizado puro. Si un componente lee de window.someGlobal o una ref mutable durante el renderizado, la salida cacheada no reflejará actualizaciones a esa global.
Solución: Usa useSyncExternalStore para tiendas externas, o añade "use no memo".
Trampa: ¿Por qué los efectos secundarios durante el renderizado podrían dejar de dispararse después de activar el compilador?
El compilador cachea la salida del componente. Si efectos secundarios (logging, solicitudes de red, mutaciones) se ejecutan durante el renderizado, pueden ser saltados cuando la salida cacheada se reutiliza.
Solución: Mueve efectos secundarios dentro de useEffect o manejadores de eventos.
¿Funciona el compilador con componentes de clase?
No. El compilador solo optimiza componentes de función y hooks. Los componentes de clase se pasan sin cambios. Migra componentes de clase a componentes de función para beneficiarte.
¿Debes eliminar useMemo y useCallback existentes antes de activar el compilador?
No. Activa el compilador primero, verifica que el rendimiento se mantenga, luego elimina la memorización manual. Eliminar hooks antes de activar el compilador deja tu aplicación sin optimizar durante la brecha.
¿El Compilador de React requiere alguna configuración especial de TypeScript?
No. El compilador funciona con TypeScript de forma inmediata. Las anotaciones de tipo se preservan -- el compilador solo transforma el comportamiento en tiempo de ejecución. Los componentes genéricos se admiten sin cambios.
¿Cómo puedes verificar qué transforma el compilador en tu código?
npx react-compiler-healthcheck
# O inspecciona la salida compilada directamente:
npx babel --plugins babel-plugin-react-compiler src/Component.tsx¿Qué hace "use memo" en el modo de compilación de anotación?
En el modo compilationMode: "annotation", solo los componentes con la directiva "use memo" se optimizan por el compilador. Esto permite la migración gradual al optar por componente.
¿Cómo difiere el caché de grano fino del compilador de useMemo manual?
useMemo manual cachea con la granularidad que especifiques (un valor por llamada a hook). El compilador puede cachear elementos JSX individuales dentro de un componente, potencialmente logrando una granularidad mejor que la optimización escrita a mano.
Relacionado
- Memorización — Patrones manuales de useMemo, useCallback y React.memo
- Prevención de Re-renderizados — Patrones estructurales que reducen re-renderizados sin memorización
- Perfilado — Verificar que el compilador está optimizando efectivamente tus componentes
- Optimización de Bundle — El compilador añade una sobrecarga de bundle mínima