useContext odczytuje wartość, którą dostarcza komponent rodzica, więc komponent głęboko w drzewie może ją dostać bez tego, żeby każdy komponent po drodze przekazywał ją jako prop. Tworzysz kontekst przez createContext, owijasz część drzewa w providera z value i wywołujesz useContext(TheContext) wszędzie tam, gdzie potrzebujesz tej wartości.
Toolbar nigdzie nie wspomina o motywie, a jednak oba przyciski zmieniają się, gdy zaznaczysz pole. O to właśnie chodzi w kontekście: komponenty pośrodku nie noszą propsa, którego nie używają, a to jest problem prop drillingu, który rozwiązuje kontekst.
Trzy kroki
// 1. Create it once, outside any component, usually in its own file
export const ThemeContext = createContext('light');
// 2. Provide a value to a part of the tree
<ThemeContext value={theme}>
<Page />
</ThemeContext>
// 3. Read it in any component inside that part
const theme = useContext(ThemeContext);
W React 19 sam obiekt kontekstu jest providerem. Starszy kod pisze <ThemeContext.Provider value={theme}>, co nadal działa, więc zobaczysz obie formy.
useContext szuka w górę drzewa najbliższego providera tego kontekstu. Komponent odczytuje najbliższy, więc możesz zagnieżdżać providery, żeby nadpisać wartość dla jednej sekcji.
Wartość domyślna
Argument createContext to to, co zwraca useContext, gdy nad komponentem nie ma żadnego providera. Nie zmienia się, gdy zmienia się stan; to wartość zapasowa.
Pierwszy Greeting nie ma nad sobą providera i dostaje domyślne 'en'. Drugi odczytuje 'es', a trzeci bliższe 'ja'. Zmień zewnętrzne value="es" na value="ja", a zmieni się tylko druga linia.
Aktualizacja kontekstu z dziecka
Kontekst tylko przekazuje wartość w dół. Żeby głęboko zagnieżdżone dziecko mogło ją zmienić, trzymaj wartość w stanie tam, gdzie jest provider, i umieść w kontekście setter obok niej.
LoginButton wywołuje setUser, stan w App się zmienia, provider dostaje nową wartość, a Header też się aktualizuje. Żaden z komponentów nie dostał propsa.
Każdy konsument renderuje się ponownie, gdy zmienia się wartość
Gdy zmienia się value providera, React renderuje każdy komponent, który wywołuje dla niego useContext, nawet jeśli ten komponent jest wewnątrz memo, a jego propsy się nie zmieniły. React porównuje starą i nową wartość przez Object.is. Dlatego value={{ user, setUser }} to nowy obiekt przy każdym renderowaniu App, a każdy konsument renderuje się ponownie, nawet gdy user się nie zmienił.
Oba komponenty czytające wypisują log raz przy pierwszym renderowaniu. Kliknij przycisk: log wypisuje ponownie tylko PlainReader, bo jego kontekst dostał nowy obiekt. useMemo przekazuje MemoContext ten sam obiekt, dopóki nie zmieni się name, więc MemoReader zostaje na miejscu. Ma to znaczenie dopiero wtedy, gdy renderowanie staje się wolne; w małej aplikacji nowy obiekt przy każdym renderowaniu jest w porządku.
Dzielenie kontekstów
Wartość, która zmienia się często, i wartość, która nigdy się nie zmienia, nie powinny dzielić jednego kontekstu. Jeśli setter mieszka razem ze stanem, komponent, który tylko wywołuje setUser, i tak renderuje się za każdym razem, gdy zmienia się user. Umieść je w dwóch kontekstach, a każdy konsument zasubskrybuje tylko to, co czyta:
const UserContext = createContext(null);
const SetUserContext = createContext(() => {});
function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<SetUserContext value={setUser}>
<UserContext value={user}>{children}</UserContext>
</SetUserContext>
);
}
setUser z useState na zawsze zachowuje tę samą tożsamość, więc komponenty, które czytają tylko SetUserContext, nigdy nie renderują się z powodu logowania. Ta sama zasada dotyczy niezwiązanych danych: motyw i koszyk zakupowy należą do osobnych kontekstów, a nie do jednego dużego AppContext.
Częstym wykończeniem jest własny hook, taki jak useUser(), który wywołuje useContext(UserContext) i rzuca czytelny błąd, gdy brakuje providera, więc wywołujący nigdy nie importują obiektu kontekstu bezpośrednio.
Kontekst, propsy czy biblioteka stanu
- Propsy to wybór domyślny. Przekazanie wartości dwa albo trzy poziomy w dół jest jasne i łatwe do prześledzenia.
- Kontekst pasuje do wartości, których potrzebuje wiele komponentów na różnych głębokościach i które rzadko się zmieniają: motyw, język, zalogowany użytkownik, obiekt flag funkcji.
- Biblioteka stanu (Redux Toolkit, Zustand, Jotai i inne) pasuje do dużego stanu, który często się aktualizuje, gdy chcesz, żeby komponent subskrybował jeden wycinek i pomijał renderowania wywołane przez resztę. Kontekst nie ma takiego selektora: konsument renderuje się przy każdej zmianie wartości.
Zanim sięgniesz po kontekst albo bibliotekę, sprawdź, czy przekazanie komponentów jako children nie usuwa drillingu. Często tak jest, bez żadnego nowego API.
Najczęściej zadawane pytania
Co robi useContext w React?
Zwraca bieżącą wartość kontekstu, pobraną z najbliższego providera nad komponentem w drzewie. Gdy wartość tego providera się zmienia, każdy komponent, który ją odczytuje, renderuje się ponownie.
Do czego służy wartość domyślna w createContext?
To ją zwraca useContext, gdy nad komponentem nie ma providera. Przydaje się w testach i dla komponentów renderowanych samodzielnie, a sama nigdy się nie zmienia.
Czy w React 19 nadal potrzebuję Context.Provider?
Nie. W React 19 możesz wyrenderować sam kontekst jako provider: <ThemeContext value="dark">. <ThemeContext.Provider value="dark"> nadal działa, więc starszy kod dalej się uruchamia.
Jak zaktualizować wartość kontekstu z komponentu dziecka?
Trzymaj wartość w stanie w komponencie, który renderuje providera, i umieść setter w kontekście obok wartości: value={{ theme, setTheme }}. Dziecko odczytuje setTheme przez useContext i go wywołuje.
Czy kontekst w React zastępuje Reduxa?
Kontekst przekazuje wartość w dół drzewa; sam nie zarządza stanem. Połączony z useState albo useReducer wystarcza w wielu aplikacjach. Biblioteka stanu dodaje to, czego kontekstowi brakuje, na przykład subskrypcję jednego wycinka, żeby niezwiązane aktualizacje nie renderowały komponentu ponownie, a do tego devtools i middleware.