Volver al blog

React Compiler en 2026: El Fin del useMemo y useCallback Manual

Hola HaWkers, uno de los mayores cambios en el ecosistema React en 2026 es la adopción masiva del React Compiler. La memoización manual que atormentó a los desarrolladores durante años finalmente se está convirtiendo en cosa del pasado.

Vamos a entender qué cambió y cómo escribir React moderno.

El Problema Que el Compiler Resuelve

El Dolor de la Memoización Manual

Durante años, los desarrolladores React tuvieron que lidiar con esto:

// El infierno de la memoización manual (como era antes)

import { useMemo, useCallback, memo } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserListProps {
  users: User[];
  onSelect: (user: User) => void;
  filter: string;
}

// Componente memoizado
const UserCard = memo(({ user, onSelect }: {
  user: User;
  onSelect: (user: User) => void;
}) => {
  return (
    <div onClick={() => onSelect(user)}>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
});

function UserList({ users, onSelect, filter }: UserListProps) {
  // useMemo para filtrar users
  const filteredUsers = useMemo(() => {
    return users.filter(u =>
      u.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [users, filter]);

  // useCallback para estabilizar referencia
  const handleSelect = useCallback((user: User) => {
    console.log('Selected:', user);
    onSelect(user);
  }, [onSelect]);

  // useMemo para computación costosa
  const stats = useMemo(() => ({
    total: users.length,
    filtered: filteredUsers.length,
    percentage: (filteredUsers.length / users.length * 100).toFixed(1)
  }), [users.length, filteredUsers.length]);

  return (
    <div>
      <p>Mostrando {stats.filtered} de {stats.total} ({stats.percentage}%)</p>
      {filteredUsers.map(user => (
        <UserCard
          key={user.id}
          user={user}
          onSelect={handleSelect}
        />
      ))}
    </div>
  );
}

Problemas de este enfoque:

  • Código verboso y difícil de leer
  • Fácil olvidar dependencias
  • Over-memoization (memoizar cosas que no lo necesitan)
  • Under-memoization (olvidar memoizar lo que necesita)
  • Difícil saber cuándo memoizar

El React Compiler

Cómo Funciona

// Con React Compiler (2026) - EL MISMO código, sin memoización manual

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserListProps {
  users: User[];
  onSelect: (user: User) => void;
  filter: string;
}

// Sin memo() - compiler optimiza automáticamente
function UserCard({ user, onSelect }: {
  user: User;
  onSelect: (user: User) => void;
}) {
  return (
    <div onClick={() => onSelect(user)}>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

// Código limpio y natural
function UserList({ users, onSelect, filter }: UserListProps) {
  // Sin useMemo - compiler detecta y optimiza
  const filteredUsers = users.filter(u =>
    u.name.toLowerCase().includes(filter.toLowerCase())
  );

  // Sin useCallback - compiler estabiliza automáticamente
  const handleSelect = (user: User) => {
    console.log('Selected:', user);
    onSelect(user);
  };

  // Sin useMemo - compiler sabe que es computación derivada
  const stats = {
    total: users.length,
    filtered: filteredUsers.length,
    percentage: (filteredUsers.length / users.length * 100).toFixed(1)
  };

  return (
    <div>
      <p>Mostrando {stats.filtered} de {stats.total} ({stats.percentage}%)</p>
      {filteredUsers.map(user => (
        <UserCard
          key={user.id}
          user={user}
          onSelect={handleSelect}
        />
      ))}
    </div>
  );
}

Lo que hace el compiler:

  1. Analiza el código en tiempo de compilación
  2. Detecta valores que pueden ser memoizados
  3. Inserta memoización automáticamente donde es necesario
  4. Garantiza que los callbacks sean estables
  5. Optimiza re-renders automáticamente

Lo Que el Compiler Analiza

Detección Inteligente

// El compiler entiende patrones comunes

function ProductPage({ productId }: { productId: string }) {
  const [quantity, setQuantity] = useState(1);

  // Compiler detecta: depende solo de productId
  // Memoiza automáticamente
  const product = products.find(p => p.id === productId);

  // Compiler detecta: depende de product y quantity
  // Memoiza y recalcula cuando es necesario
  const totalPrice = product ? product.price * quantity : 0;

  // Compiler detecta: función que se pasa como prop
  // Estabiliza referencia automáticamente
  const handleAddToCart = () => {
    addToCart(productId, quantity);
  };

  // Compiler detecta: objeto creado en render
  // Memoiza para evitar re-renders de hijos
  const cartItem = {
    productId,
    quantity,
    price: totalPrice
  };

  return (
    <div>
      <h1>{product?.name}</h1>
      <QuantitySelector
        value={quantity}
        onChange={setQuantity}  // setter es estable por naturaleza
      />
      <p>Total: ${totalPrice}</p>
      <AddToCartButton
        item={cartItem}       // objeto memoizado
        onAdd={handleAddToCart} // callback estabilizado
      />
    </div>
  );
}

Configuración en 2026

Setup Estándar

// next.config.ts (Next.js 15+)
import type { NextConfig } from 'next';

const config: NextConfig = {
  experimental: {
    // React Compiler habilitado por defecto en 2026
    reactCompiler: true,
  },
};

export default config;

// Para proyectos Vite
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import reactCompiler from 'babel-plugin-react-compiler';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [reactCompiler],
      },
    }),
  ],
});

ESLint Plugin

// eslint.config.js
import reactCompiler from 'eslint-plugin-react-compiler';

export default [
  {
    plugins: {
      'react-compiler': reactCompiler,
    },
    rules: {
      // Avisa cuando el código puede ser problemático
      'react-compiler/react-compiler': 'error',
    },
  },
];

Cuándo Todavía Usar Hooks Manuales

Casos Especiales

// Casos donde la memoización manual todavía tiene sentido

// 1. Computaciones MUY costosas con control fino
function DataVisualization({ data }: { data: number[] }) {
  // Para cálculos que toman segundos, podrías
  // querer control explícito
  const processedData = useMemo(() => {
    return expensiveStatisticalAnalysis(data);
  }, [data]);

  // O usar la nueva API useDeferredValue para no bloquear
  const deferredData = useDeferredValue(data);

  return <Chart data={processedData} />;
}

// 2. Integración con bibliotecas externas
function MapComponent({ markers }: { markers: Marker[] }) {
  // Bibliotecas externas pueden tener sus propias
  // reglas de comparación
  const memoizedMarkers = useMemo(
    () => markers.map(m => createMapMarker(m)),
    [markers]
  );

  return <ExternalMapLibrary markers={memoizedMarkers} />;
}

// 3. Refs e imperative handles
function VideoPlayer({ src }: { src: string }) {
  const videoRef = useRef<HTMLVideoElement>(null);

  // useCallback todavía útil para APIs imperativas
  const play = useCallback(() => {
    videoRef.current?.play();
  }, []);

  const pause = useCallback(() => {
    videoRef.current?.pause();
  }, []);

  // Exponiendo métodos imperativos
  useImperativeHandle(ref, () => ({
    play,
    pause,
  }), [play, pause]);

  return <video ref={videoRef} src={src} />;
}

Patrones Modernos en 2026

Server Components + Compiler

// Combinando React Server Components con Compiler

// app/products/page.tsx (Server Component)
async function ProductsPage() {
  // Datos obtenidos en el servidor
  const products = await fetchProducts();

  return (
    <div>
      <h1>Productos</h1>
      {/* Client Component recibe datos serializados */}
      <ProductGrid products={products} />
    </div>
  );
}

// components/ProductGrid.tsx (Client Component)
'use client';

function ProductGrid({ products }: { products: Product[] }) {
  const [filter, setFilter] = useState('');
  const [sort, setSort] = useState<'price' | 'name'>('name');

  // Compiler optimiza todo automáticamente
  const filtered = products.filter(p =>
    p.name.toLowerCase().includes(filter.toLowerCase())
  );

  const sorted = [...filtered].sort((a, b) => {
    if (sort === 'price') return a.price - b.price;
    return a.name.localeCompare(b.name);
  });

  return (
    <div>
      <FilterInput value={filter} onChange={setFilter} />
      <SortSelect value={sort} onChange={setSort} />
      <div className="grid">
        {sorted.map(product => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  );
}

Actions y Mutations

// Patrón moderno con Server Actions

// actions/cart.ts
'use server';

import { revalidatePath } from 'next/cache';

export async function addToCart(productId: string, quantity: number) {
  await db.cart.add({ productId, quantity });
  revalidatePath('/cart');
}

// components/AddToCartButton.tsx
'use client';

import { addToCart } from '@/actions/cart';
import { useTransition } from 'react';

function AddToCartButton({ productId }: { productId: string }) {
  const [isPending, startTransition] = useTransition();
  const [quantity, setQuantity] = useState(1);

  // Compiler optimiza automáticamente
  const handleClick = () => {
    startTransition(async () => {
      await addToCart(productId, quantity);
    });
  };

  return (
    <button onClick={handleClick} disabled={isPending}>
      {isPending ? 'Agregando...' : 'Agregar al Carrito'}
    </button>
  );
}

Migrando Código Legacy

Estrategia de Migración

// Paso 1: Habilita el compiler en el proyecto

// Paso 2: Elimina memoización innecesaria gradualmente

// ANTES
const MemoizedComponent = memo(function Component({ data }) {
  const processed = useMemo(() => process(data), [data]);
  const handler = useCallback(() => doSomething(), []);
  return <div onClick={handler}>{processed}</div>;
});

// DESPUÉS
function Component({ data }) {
  const processed = process(data);
  const handler = () => doSomething();
  return <div onClick={handler}>{processed}</div>;
}

// Paso 3: Confía en el plugin ESLint para avisar problemas

// Paso 4: Prueba rendimiento antes y después

Qué Eliminar

// Checklist de migración

const migrationChecklist = {
  // Se puede eliminar con seguridad
  safeToRemove: [
    'React.memo() en componentes simples',
    'useMemo() para objetos pasados como props',
    'useCallback() para handlers de evento',
    'useMemo() para computaciones derivadas simples'
  ],

  // Evaluar caso por caso
  evaluate: [
    'useMemo() para computaciones muy costosas',
    'useCallback() en bibliotecas externas',
    'memo() con función de comparación personalizada'
  ],

  // Mantener
  keep: [
    'useRef() - no es memoización',
    'useState() - no es memoización',
    'useEffect() - side effects',
    'useLayoutEffect() - side effects síncronos'
  ]
};

Rendimiento en 2026

Comparativo

// Resultados típicos de migración

const performanceComparison = {
  bundleSize: {
    before: '145kb',
    after: '142kb',
    note: 'Compiler agrega poco overhead'
  },

  developerExperience: {
    before: 'Muchos bugs de dependencias olvidadas',
    after: 'Cero preocupación con memoización',
    timesSaved: 'Significativo'
  },

  runtime: {
    before: 'Rendimiento inconsistente',
    after: 'Optimización automática consistente',
    improvement: '10-30% en apps con memoización incorrecta'
  },

  codeReadability: {
    before: 'Contaminado con hooks de memoización',
    after: 'Limpio y directo al punto',
    improvement: 'Significativa'
  }
};

Conclusión

El React Compiler representa un cambio de paradigma: de "¿cómo optimizo esto?" a "escribe código natural y deja que el compiler optimice".

Principales conclusiones:

  1. La memoización manual es cosa del pasado - El compiler lo hace mejor que los humanos
  2. Código más limpio - Enfócate en la lógica, no en la optimización
  3. Menos bugs - Sin errores de dependencias olvidadas
  4. Rendimiento consistente - Optimización automática e inteligente

Si todavía no habilitaste el React Compiler en tu proyecto, 2026 es el año de hacerlo. La comunidad React ya lo adoptó masivamente, y los frameworks principales tienen soporte first-class.

Para entender más sobre el ecosistema JavaScript actual, consulta: TypeScript Es el Estándar en 2026.

¡Vamos con todo! 🦅

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios