Nowe projekty to bajka. Zaczynacie od czystej kartki, instalujecie React Compiler od razu, konfigurujecie ESLint i wszystko działa. Ale większość z nas nie ma tego luksusu.
Mam w swoim portfolio projekty, które zaczęły się w czasach, gdy useMemo i useCallback były jedynym sposobem na optymalizację. Kod pisany przez kilka lat, przez kilka zespołów, z różnymi standardami — albo bez nich. I właśnie dla takich projektów ten wpis powstał.
Pokażę Wam, jak podejść do wdrożenia React Compilera nie w idealnym świecie, ale w tym prawdziwym — gdzie macie 300 komponentów, połowa naruszeń Rules of React jest nieświadoma, a testy… są albo ich nie ma.
1. Dlaczego legacy to osobny przypadek
React Compiler działa najlepiej, gdy kod przestrzega Rules of React. W nowych projektach, gdzie od początku macie ESLint z odpowiednimi regułami, to naturalne. W projektach legacy — niekoniecznie.
Co typowo spotyka się w starszych bazach kodu:
Mutowanie stanu
// Stary nawyk — modyfikowanie obiektu bezpośrednio
const handleUpdate = () => {
user.name = 'Anna'; // ❌
setUser(user);
};
Efekty uboczne w renderze
// "Tymczasowe" logowanie, które zostało na zawsze
function UserCard({ user }) {
console.log('render', user.id); // ❌ side effect w renderze
analytics.track('view'); // ❌ to samo
return <div>{user.name}</div>;
}
Niestabilne referencje w zależnościach
useEffect(() => {
fetchData(config);
}, [config]); // ❌ jeśli config jest tworzony inline przy każdym renderze
Nieużywane, ale “bezpieczne” useMemo
// Ktoś dodał "na wszelki wypadek" — kompilator może to zignorować
const value = useMemo(() => data, []); // ❌ pusta tablica zależności + zmienna z zewnątrz
W nowym projekcie te problemy nie istnieją lub są wyłapywane na bieżąco. W legacy — są wszędzie i często nikt już nie pamięta, dlaczego dany fragment jest napisany tak, a nie inaczej.
Dobra wiadomość: React Compiler ma na to odpowiedź. Ma tryb, który pozwala wdrażać go stopniowo, komponent po komponencie.
2. Zanim zaczniesz — audyt projektu
Nie zaczynaj od konfiguracji. Zacznij od danych.
Krok 1: Zainstaluj eslint-plugin-react-compiler
pnpm install -D eslint-plugin-react-compiler
Jeśli używacie flat config (ESLint 9+) w pliku musi się znaleźć:
// eslint.config.js
import reactCompiler from 'eslint-plugin-react-compiler';
export default [
{
plugins: {
'react-compiler': reactCompiler,
},
rules: {
'react-compiler/react-compiler': 'error',
},
},
];
Jeśli używacie starszego formatu .eslintrc:
{
"plugins": ["react-compiler"],
"rules": {
"react-compiler/react-compiler": "error"
}
}
Krok 2: Zbierz raport
npx eslint src --format json > react-compiler-audit.json
Nie musicie tego parsować ręcznie. Możecie użyć prostego skryptu:
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('react-compiler-audit.json'));
const issues = data
.filter(file => file.messages.length > 0)
.map(file => ({
path: file.filePath,
count: file.messages.filter(m => m.ruleId === 'react-compiler/react-compiler').length,
messages: file.messages
.filter(m => m.ruleId === 'react-compiler/react-compiler')
.map(m => m.message)
}))
.filter(f => f.count > 0)
.sort((a, b) => b.count - a.count);
console.log(`Łączna liczba naruszeń: ${issues.reduce((sum, f) => sum + f.count, 0)}`);
console.log(`Pliki z naruszeniami: ${issues.length}`);
console.log('\nNajbardziej problematyczne pliki:');
issues.slice(0, 10).forEach(f => {
console.log(` ${f.count}x ${f.path.split('/src/')[1]}`);
});
Uruchomienie skryptu:
node audit-summary.js
Krok 3: Oceń skalę
Wynik audytu powie Wam bardzo dużo. Na przykład w projekcie, który niedawno stworzyłam i opisywałam tutaj jako demo React Compiler mamy:
Łączna liczba naruszeń: 7
Pliki z naruszeniami: 1
Najbardziej problematyczne pliki:
7x BadExamples.tsx
Ale w prawdziwym projekcie to może wyglądać np tak:
Łączna liczba naruszeń: 47
Pliki z naruszeniami: 23
Najbardziej problematyczne pliki:
8x xxx.tsx
6x xxy.tsx
4x xxz.tsx
...
To nie jest katastrofa — 47 naruszeń w 23 plikach przy bazie 300+ komponentów to oznacza, że ponad 90% kodu jest gotowe na kompilator od zaraz.
Co oznaczają liczby?
| Wynik | Co to znaczy |
|---|---|
| 0–20 naruszeń | Możecie rozważyć włączenie kompilatora globalnie od razu |
| 20–100 naruszeń | Podejście iteracyjne, kilka tygodni pracy |
| 100+ naruszeń | Długoterminowy plan migracji, zacznijcie od opt-in |
3. Strategia wdrożenia — tryb opt-in
React Compiler oferuje trzy tryby pracy:
infer(domyślny) — kompilator sam decyduje, co optymalizowaćannotation— optymalizuje tylko komponenty z dyrektywąuse memoall— optymalizuje wszystko
Dla projektów legacy tryb annotation jest najbezpieczniejszy. Dajecie kompilatorowi wyraźny sygnał: “optymalizuj tylko to, co ci wskażę”.
Instalacja
pnpm install -D babel-plugin-react-compiler
Konfiguracja w trybie annotation
Jeśli używacie Vite:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
['babel-plugin-react-compiler', {
compilationMode: 'annotation',
}],
],
},
}),
],
});
Jeśli używacie Next.js:
// next.config.js
const nextConfig = {
experimental: {
reactCompiler: {
compilationMode: 'annotation',
},
},
};
Dyrektywy opt-in i opt-out
// Opt-in: "skompiluj ten komponent"
function UserCard({ user }) {
'use memo';
return <div>{user.name}</div>;
}
// Opt-out: "nie dotykaj tego komponentu"
function LegacyWidget({ data }) {
'use no memo';
<em>// stary kod, który jeszcze nie jest gotowy</em>
return <div>{data}</div>;
}
W trybie infer (gdy chcecie kompilować wszystko poza wyjątkami) "use no memo" jest szczególnie przydatne — możecie globalnie włączyć kompilator i wykluczyć tylko te komponenty, które jeszcze nie są gotowe.
4. Krok po kroku — iteracyjne wdrożenie
Mam sprawdzoną kolejność, która minimalizuje ryzyko.
Etap 1: Nowe komponenty (tydzień 1)
Zacznijcie od komponentów, które tworzycie teraz. Zero ryzyka — piszecie je od początku z myślą o kompilatorze.
Zasada: każdy nowy komponent dostaje use memo i jest pisany zgodnie z Rules of React.
To jest też moment, żeby wyrobić nawyk w zespole.
Etap 2: Izolowane, dobrze przetestowane komponenty (tydzień 2–3)
Wróćcie do wyników audytu. Znajdźcie komponenty:
- bez naruszeń ESLint
- z dobrym pokryciem testami
- możliwie izolowane (mało zależności od reszty)
To są Wasi “bezpieczni kandydaci”. Dodajcie im use memo i obserwujcie:
npm test -- --watch
Włączcie też React DevTools Profiler i zróbcie baseline przed i po — za chwilę wrócimy do mierzenia efektów.
Etap 3: Komponenty z naruszeniami — po naprawie (tydzień 4+)
Każde naruszenie ESLint to zadanie do zrobienia. Naprawcie je, a potem dodajcie use memo.
Nie próbujcie naprawiać wszystkiego na raz. Idźcie plik po pliku, zaczynając od tych z najmniejszą liczbą naruszeń.
// Przed naprawą
function Cart({ items }) {
const total = { value: 0, currency: 'PLN' }; // ❌ nowy obiekt przy każdym renderze
items.forEach(item => { total.value += item.price }); // ❌ mutacja
return <div>{total.value} {total.currency}</div>;
}
// Po naprawie
function Cart({ items }) {
'use memo';
const total = {
value: items.reduce((sum, item) => sum + item.price, 0),
currency: 'PLN'
};
return <div>{total.value} {total.currency}</div>;
}
Zwróćcie uwagę — nie ma tu już useMemo. React Compiler zadba o memoizację sam, więc przy okazji naprawiania naruszeń możecie od razu pozbywać się ręcznych useMemo i useCallback.
Etap 4: Przejście na tryb infer (opcjonalne)
Gdy większość komponentów jest naprawiona i przetestowana, możecie zmienić tryb na infer i dodać use no memo tylko do tych, które jeszcze nie są gotowe.
// vite.config.ts — zmiana z annotation na infer</em>
['babel-plugin-react-compiler', {
compilationMode: 'infer', // kompiluje wszystko, co spełnia Rules of React
}],
To naturalny punkt docelowy — kompilator sam decyduje i nie musicie ręcznie oznaczać każdego komponentu.
5. Pułapki, na które możecie się natknąć
Zewnętrzne biblioteki, które łamią reguły
To największy ból głowy. Biblioteki komponentów (szczególnie starsze wersje) często nie przestrzegają Rules of React wewnętrznie.
Symptom: po włączeniu use memo w komponencie, który używa zewnętrznej biblioteki, zaczynają się dziwne bugi — komponenty nie aktualizują się, albo aktualizują się za często.
Rozwiązanie: use no memo na wrappery wokół problematycznych bibliotek, dopóki biblioteka nie zostanie zaktualizowana lub zastąpiona.
// Wrapper wokół starszej biblioteki
function LegacyChartWrapper({ data }) {
'use no memo'; // dopóki biblioteka nie jest kompatybilna
return <OldChartLibrary data={data} />;
}
Konteksty z niestabilnymi wartościami
// ❌ Nowy obiekt przy każdym renderze providera
function AppProvider({ children }) {
const [user, setUser] = useState(null);
return (
<AppContext.Provider value={{ user, setUser }}> {/* nowy obiekt przy każdym renderze */}
{children}
</AppContext.Provider>
);
}
// ✅ React Compiler sam to zoptymalizuje — nie trzeba ręcznie dodawać useMemo
function AppProvider({ children }) {
const [user, setUser] = useState(null);
return (
<AppContext.Provider value={{ user, setUser }}>
{children}
</AppContext.Provider>
);
}
Jeśli kompilator jest włączony, nie trzeba ręcznie owijać w useMemo.
Efekty uboczne w obliczeniach
// ❌ Kompilator nie zoptymalizuje — side effect w "czystej" funkcji
const processedData = useMemo(() => {
logToAnalytics('processing'); // ❌ side effect
return data.map(transform);
}, [data]);
// ✅ Side effecty oddzielone od obliczeń</em>
useEffect(() => {
logToAnalytics('processing');
}, [data]);
const processedData = data.map(transform); // useMemo jest tu zbędne — kompilator zajmie się memoizacją sam.
useRef jako mutable state
<em>// ❌ Częsty antywzorzec w starszym kodzie</em>
function Timer() {
const count = useRef(0);
const tick = () => {
count.current++; <em>// ❌ mutacja ref używana do śledzenia stanu UI</em>
setDisplay(count.current);
};
}
<em>// ✅ Stan do stanu, ref do refów</em>
function Timer() {
const [count, setCount] = useState(0);
const tick = () => {
setCount(c => c + 1);
};
}
6. Jak mierzyć efekty
Nie wdrażajcie kompilatora bez mierzenia — inaczej nie wiecie, czy cokolwiek zrobiliście dobrego.
React DevTools Profiler
- Otwórzcie DevTools → zakładka Profiler
- Kliknijcie “Record”
- Wykonajcie kilka typowych akcji w aplikacji (nawigacja, interakcje z formularzami, aktualizacje danych)
- Zatrzymajcie nagrywanie
- Zapiszcie wyniki jako baseline
Powtórzcie po wdrożeniu kompilatora w danym obszarze.
Co możecie zaobserwować przede wszystkim:
- Liczba re-renderów — powinna spaść dla komponentów zależnych od zoptymalizowanych
- Czas renderowania — dla ciężkich komponentów z kosztownymi obliczeniami
- “Wasted renders” — komponenty, które renderują się bez zmiany propsów
Prosta miara: liczba zoptymalizowanych komponentów
React Compiler oznacza zoptymalizowane komponenty w DevTools — zobaczycie ikonkę ✨ przy nazwie komponentu. To dobry wskaźnik postępu migracji.
Mierzenie daje Wam również konkretne argumenty do rozmowy z zespołem i product ownerem.
7. Podsumowanie — checklista wdrożenia
Wklejcie do Notion, Jiry albo wydrukujcie i powieście na monitorze 😄
Przygotowanie
- [ ] Zainstaluj
eslint-plugin-react-compiler - [ ] Uruchom audyt ESLint i zapisz raport
- [ ] Policz naruszenia i oceń skalę (< 20 / 20–100 / 100+)
- [ ] Pogrupuj naruszenia według typu
- [ ] Zidentyfikuj “bezpiecznych kandydatów” (bez naruszeń + testy)
Konfiguracja
- [ ] Zainstaluj
babel-plugin-react-compiler - [ ] Skonfiguruj w trybie
annotation(dla legacy) - [ ] Sprawdź, czy build działa poprawnie
- [ ] Zrób baseline w React DevTools Profiler
Iteracyjne wdrożenie
- [ ] Etap 1: Nowe komponenty oznaczaj
"use memo"od razu - [ ] Etap 2: Dodaj
"use memo"do bezpiecznych kandydatów - [ ] Etap 3: Napraw naruszenia ESLint, zanim dodasz
"use memo" - [ ] Etap 4: Przetestuj po każdym pliku / module
- [ ] Etap 5 (opcjonalne): Przejdź na tryb
infergdy większość jest gotowa
Weryfikacja
- [ ] Testy automatyczne przechodzą
- [ ] Profilowanie pokazuje poprawę (lub brak regresji)
- [ ] Ręczne testy kluczowych flow aplikacji
- [ ] Code review z uwzględnieniem nowych wzorców
Wdrożenie React Compilera w legacy projekcie to maraton, nie sprint. Ale iteracyjne podejście — zaczynając od audytu, przez opt-in, po stopniowe rozszerzanie zakresu — pozwala robić to bezpiecznie i bez rewolucji.
To piąty wpis z serii o React Compiler. Poprzednie:


