Wydajne providery w React.js
Context API to jedna z podstawowych funkcjonalności w Reakcie. Poznaj go lepiej, by pisać bardziej świadomy i wydajny kod.
Context API w React jest super przydatne, ale łatwo można z nim przesadzić. Widziałem już niejedną aplikację, która zwalniała przez źle użyty Context. Dzisiaj pokażę Ci, jak używać go mądrze i unikać typowych pułapek.
Problem z nadmiernym re-renderowaniem
W jednym z projektów miałem Context, który przechowywał cały stan aplikacji – użytkownik, preferencje, notyfikacje, wszystko w jednym miejscu. Brzmi wygodnie, prawda? Problem był taki, że każda zmiana w tym kontekście powodowała re-render wszystkich komponentów, które go używały.
Nawet jeśli komponent potrzebował tylko informacji o użytkowniku, a zmieniała się notyfikacja, i tak się renderował. W efekcie aplikacja była wolna, szczególnie na słabszych urządzeniach.
💡 Złota zasada:
Dziel Context na mniejsze, logiczne części. Zamiast jednego dużego Context, użyj kilku mniejszych, które odpowiadają za konkretne domeny (user, theme, notifications, etc.).
Jak poprawnie strukturyzować Context
Najlepsze podejście, które sprawdziło się u mnie w wielu projektach:
- Jeden Context = jedna odpowiedzialność – jeśli Context przechowuje dane użytkownika i ustawienia, to już za dużo. Podziel to.
- Używaj useMemo i useCallback – jeśli przekazujesz obiekty lub funkcje przez Context, pamiętaj o memoizacji. Inaczej każdy render tworzy nowe referencje i komponenty się re-renderują.
- Dziel wartości i funkcje – jeśli Context ma zarówno wartości, jak i funkcje do ich modyfikacji, rozdziel je na dwa Contexty. Komponenty, które tylko czytają, nie będą się renderować przy zmianie funkcji.
Praktyczny przykład z mojego projektu
Miałem aplikację z Contextem dla autentykacji. Wyglądało to mniej więcej tak:
// ❌ Złe podejście
const AuthContext = createContext({
user: null,
token: null,
login: () => {},
logout: () => {},
isLoading: false
})Problem był taki, że każda zmiana w tym kontekście (np. isLoading) powodowała re-render wszystkich komponentów używających usera. Nawet jeśli komponent tylko wyświetlał imię użytkownika.
Rozwiązałem to tak:
// ✅ Lepsze podejście
// Dzielę na dwa Contexty
const UserContext = createContext(user)
const AuthActionsContext = createContext({ login, logout })
// W komponencie używam tylko tego, czego potrzebuję
const user = useContext(UserContext) // Nie re-renderuje się przy zmianie isLoading
const { login } = useContext(AuthActionsContext)useMemo i useCallback – kiedy używać?
To jest kluczowe. Jeśli przekazujesz przez Context obiekt lub funkcję, która jest tworzona w każdym renderze, komponenty będą się re-renderować nawet jeśli wartości się nie zmieniły.
Przykład z życia: miałem Context z konfiguracją API. Wyglądało to tak:
// ❌ Problem
const ApiContext = createContext({
config: {
baseUrl: 'https://api.example.com',
timeout: 5000
},
fetchData: (endpoint) => {
// fetch logic
}
})
// W każdym renderze tworzy się nowy obiekt config
// i nowa funkcja fetchData → wszystkie komponenty się re-renderująNaprawiłem to przez memoizację:
// ✅ Rozwiązanie
const config = useMemo(() => ({
baseUrl: 'https://api.example.com',
timeout: 5000
}), [])
const fetchData = useCallback((endpoint) => {
// fetch logic
}, [])
const value = useMemo(() => ({
config,
fetchData
}), [config, fetchData])Teraz obiekt config i funkcja fetchData mają stabilne referencje i komponenty nie re-renderują się niepotrzebnie.
Kiedy NIE używać Context API?
Context nie jest rozwiązaniem na wszystko. Widziałem projekty, gdzie Context był używany do przekazywania propsów przez 2-3 poziomy komponentów. To overkill.
Używaj Context, gdy:
- Dane są potrzebne w wielu miejscach w aplikacji (np. stan użytkownika, motyw)
- Props drilling staje się uciążliwy (więcej niż 3-4 poziomy)
- Dane są globalne i rzadko się zmieniają
Nie używaj Context, gdy:
- Dane są potrzebne tylko w jednym miejscu lub w komponencie i jego bezpośrednich dzieciach
- Dane zmieniają się bardzo często (lepiej użyć state management jak Zustand, Redux)
- Chcesz uniknąć re-renderów (Context zawsze powoduje re-render konsumentów przy zmianie)
Alternatywy dla Context API
W niektórych przypadkach Context nie jest najlepszym rozwiązaniem. Oto co sprawdziło się u mnie:
- Zustand – świetny do globalnego state management. Lżejszy niż Redux, prostszy w użyciu niż Context. Używam go, gdy potrzebuję złożonego stanu, który często się zmienia.
- React Query / SWR – do zarządzania danymi z API. Automatycznie cache'uje, synchronizuje, invaliduje. Oszczędza mnóstwo boilerplate'u.
- Props drilling – czasem po prostu przekazanie propsów przez kilka poziomów jest OK. Nie wszystko musi być w Context.
Best practices – podsumowanie
Na koniec, kilka rzeczy, które zawsze robię przy pracy z Context:
- Dziel Context na mniejsze części – jeden Context = jedna odpowiedzialność
- Memoizuj wartości i funkcje – używaj useMemo i useCallback, żeby uniknąć niepotrzebnych re-renderów
- Rozdziel wartości i akcje – komponenty, które tylko czytają, nie powinny się renderować przy zmianie funkcji
- Używaj custom hooków – zamiast useContext bezpośrednio w komponentach, stwórz custom hook. Łatwiej testować i zmieniać implementację
- Mierz wydajność – użyj React DevTools Profiler, żeby zobaczyć, które komponenty się renderują i dlaczego
⚠️ Częsty błąd:
Tworzenie jednego ogromnego Context z całym stanem aplikacji. To prowadzi do niepotrzebnych re-renderów i problemów z wydajnością. Zawsze dziel na mniejsze, logiczne części.
Context API to potężne narzędzie, ale trzeba używać go z głową. Jeśli masz pytania albo chcesz podzielić się swoimi doświadczeniami, daj znać. Zawsze chętnie wymienię się wiedzą!