Retour au blog

React Compiler en 2026 : La Fin du useMemo et useCallback Manuel

Salut HaWkers, l'un des plus grands changements dans l'écosystème React en 2026 est l'adoption massive du React Compiler. La mémoïsation manuelle qui tourmentait les développeurs depuis des années devient enfin chose du passé.

Voyons ce qui a changé et comment écrire du React moderne.

Le Problème Que le Compiler Résout

La Douleur de la Mémoïsation Manuelle

Pendant des années, les développeurs React ont dû gérer ceci :

// L'enfer de la mémoïsation manuelle (comme c'était avant)

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

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

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

// Composant mémoïsé
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 pour filtrer les users
  const filteredUsers = useMemo(() => {
    return users.filter(u =>
      u.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [users, filter]);

  // useCallback pour stabiliser la référence
  const handleSelect = useCallback((user: User) => {
    console.log('Selected:', user);
    onSelect(user);
  }, [onSelect]);

  // useMemo pour calcul coûteux
  const stats = useMemo(() => ({
    total: users.length,
    filtered: filteredUsers.length,
    percentage: (filteredUsers.length / users.length * 100).toFixed(1)
  }), [users.length, filteredUsers.length]);

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

Problèmes de cette approche :

  • Code verbeux et difficile à lire
  • Facile d'oublier les dépendances
  • Sur-mémoïsation (mémoïser des choses qui n'en ont pas besoin)
  • Sous-mémoïsation (oublier de mémoïser ce qui en a besoin)
  • Difficile de savoir quand mémoïser

Le React Compiler

Comment Ça Fonctionne

// Avec React Compiler (2026) - LE MÊME code, sans mémoïsation manuelle

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

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

// Sans memo() - le compiler optimise automatiquement
function UserCard({ user, onSelect }: {
  user: User;
  onSelect: (user: User) => void;
}) {
  return (
    <div onClick={() => onSelect(user)}>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

// Code propre et naturel
function UserList({ users, onSelect, filter }: UserListProps) {
  // Sans useMemo - le compiler détecte et optimise
  const filteredUsers = users.filter(u =>
    u.name.toLowerCase().includes(filter.toLowerCase())
  );

  // Sans useCallback - le compiler stabilise automatiquement
  const handleSelect = (user: User) => {
    console.log('Selected:', user);
    onSelect(user);
  };

  // Sans useMemo - le compiler sait que c'est un calcul dérivé
  const stats = {
    total: users.length,
    filtered: filteredUsers.length,
    percentage: (filteredUsers.length / users.length * 100).toFixed(1)
  };

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

Ce que fait le compiler :

  1. Analyse le code au moment de la compilation
  2. Détecte les valeurs qui peuvent être mémoïsées
  3. Insère automatiquement la mémoïsation où c'est nécessaire
  4. Garantit que les callbacks sont stables
  5. Optimise automatiquement les re-renders

Ce Que le Compiler Analyse

Détection Intelligente

// Le compiler comprend les patterns courants

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

  // Compiler détecte : dépend uniquement de productId
  // Mémoïse automatiquement
  const product = products.find(p => p.id === productId);

  // Compiler détecte : dépend de product et quantity
  // Mémoïse et recalcule quand nécessaire
  const totalPrice = product ? product.price * quantity : 0;

  // Compiler détecte : fonction passée en prop
  // Stabilise automatiquement la référence
  const handleAddToCart = () => {
    addToCart(productId, quantity);
  };

  // Compiler détecte : objet créé pendant le render
  // Mémoïse pour éviter les re-renders des enfants
  const cartItem = {
    productId,
    quantity,
    price: totalPrice
  };

  return (
    <div>
      <h1>{product?.name}</h1>
      <QuantitySelector
        value={quantity}
        onChange={setQuantity}  // setter est stable par nature
      />
      <p>Total : ${totalPrice}</p>
      <AddToCartButton
        item={cartItem}       // objet mémoïsé
        onAdd={handleAddToCart} // callback stabilisé
      />
    </div>
  );
}

Configuration en 2026

Setup Standard

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

const config: NextConfig = {
  experimental: {
    // React Compiler activé par défaut en 2026
    reactCompiler: true,
  },
};

export default config;

// Pour les projets 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],
      },
    }),
  ],
});

Plugin ESLint

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

export default [
  {
    plugins: {
      'react-compiler': reactCompiler,
    },
    rules: {
      // Avertit quand le code peut être problématique
      'react-compiler/react-compiler': 'error',
    },
  },
];

Quand Utiliser Encore les Hooks Manuels

Cas Spéciaux

// Cas où la mémoïsation manuelle a encore du sens

// 1. Calculs TRÈS coûteux avec contrôle fin
function DataVisualization({ data }: { data: number[] }) {
  // Pour des calculs qui prennent des secondes, vous pourriez
  // vouloir un contrôle explicite
  const processedData = useMemo(() => {
    return expensiveStatisticalAnalysis(data);
  }, [data]);

  // Ou utiliser la nouvelle API useDeferredValue pour ne pas bloquer
  const deferredData = useDeferredValue(data);

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

// 2. Intégration avec des bibliothèques externes
function MapComponent({ markers }: { markers: Marker[] }) {
  // Les bibliothèques externes peuvent avoir leurs propres
  // règles de comparaison
  const memoizedMarkers = useMemo(
    () => markers.map(m => createMapMarker(m)),
    [markers]
  );

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

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

  // useCallback toujours utile pour les APIs impératives
  const play = useCallback(() => {
    videoRef.current?.play();
  }, []);

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

  // Exposition des méthodes impératives
  useImperativeHandle(ref, () => ({
    play,
    pause,
  }), [play, pause]);

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

Patterns Modernes en 2026

Server Components + Compiler

// Combinaison de React Server Components avec le Compiler

// app/products/page.tsx (Server Component)
async function ProductsPage() {
  // Données récupérées côté serveur
  const products = await fetchProducts();

  return (
    <div>
      <h1>Produits</h1>
      {/* Client Component reçoit les données sérialisées */}
      <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');

  // Le compiler optimise tout automatiquement
  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 et Mutations

// Pattern moderne avec 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);

  // Le compiler optimise automatiquement
  const handleClick = () => {
    startTransition(async () => {
      await addToCart(productId, quantity);
    });
  };

  return (
    <button onClick={handleClick} disabled={isPending}>
      {isPending ? 'Ajout en cours...' : 'Ajouter au Panier'}
    </button>
  );
}

Migration du Code Legacy

Stratégie de Migration

// Étape 1 : Activez le compiler dans le projet

// Étape 2 : Supprimez graduellement la mémoïsation inutile

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

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

// Étape 3 : Faites confiance au plugin ESLint pour signaler les problèmes

// Étape 4 : Testez la performance avant et après

Quoi Supprimer

// Checklist de migration

const migrationChecklist = {
  // Peut être supprimé en toute sécurité
  safeToRemove: [
    'React.memo() sur les composants simples',
    'useMemo() pour les objets passés en props',
    'useCallback() pour les handlers d\'événements',
    'useMemo() pour les calculs dérivés simples'
  ],

  // À évaluer au cas par cas
  evaluate: [
    'useMemo() pour les calculs très coûteux',
    'useCallback() dans les bibliothèques externes',
    'memo() avec fonction de comparaison personnalisée'
  ],

  // À garder
  keep: [
    'useRef() - ce n\'est pas de la mémoïsation',
    'useState() - ce n\'est pas de la mémoïsation',
    'useEffect() - effets de bord',
    'useLayoutEffect() - effets de bord synchrones'
  ]
};

Performance en 2026

Comparatif

// Résultats typiques de migration

const performanceComparison = {
  bundleSize: {
    before: '145kb',
    after: '142kb',
    note: 'Le compiler ajoute peu d\'overhead'
  },

  developerExperience: {
    before: 'Nombreux bugs de dépendances oubliées',
    after: 'Zéro souci de mémoïsation',
    timesSaved: 'Significatif'
  },

  runtime: {
    before: 'Performance inconsistante',
    after: 'Optimisation automatique consistante',
    improvement: '10-30% dans les apps avec mémoïsation incorrecte'
  },

  codeReadability: {
    before: 'Pollué par les hooks de mémoïsation',
    after: 'Propre et direct',
    improvement: 'Significative'
  }
};

Conclusion

Le React Compiler représente un changement de paradigme : de "comment optimiser ceci ?" à "écrivez du code naturel et laissez le compiler optimiser".

Points clés à retenir :

  1. La mémoïsation manuelle appartient au passé - Le compiler fait mieux que les humains
  2. Code plus propre - Concentrez-vous sur la logique, pas sur l'optimisation
  3. Moins de bugs - Plus d'erreurs de dépendances oubliées
  4. Performance consistante - Optimisation automatique et intelligente

Si vous n'avez pas encore activé le React Compiler dans votre projet, 2026 est l'année pour le faire. La communauté React l'a déjà adopté massivement, et les principaux frameworks ont un support first-class.

Pour en savoir plus sur l'écosystème JavaScript actuel, consultez : TypeScript Est le Standard en 2026.

Allons-y ! 🦅

Commentaires (0)

Cet article n'a pas encore de commentaires. Soyez le premier!

Ajouter des commentaires