SomeContext: Контекст, который вы ранее создали с помощью createContext. Сам контекст не хранит информацию, он только представляет тип информации, которую вы можете предоставить или прочитать из компонентов.
useContext возвращает значение контекста для вызывающего компонента. Оно определяется как значение, переданное ближайшему SomeContext.Provider над вызывающим компонентом в дереве. Если такого провайдера нет, то возвращаемое значение будет defaultValue, которое вы передали в createContext для этого контекста. Возвращаемое значение всегда актуально. React автоматически перерисовывает компоненты, которые читают некоторый контекст, если он меняется.
Вызов useContext() в компоненте не влияет на провайдеров, возвращаемых из этого же компонента. Соответствующий <Context.Provider>должен находиться выше компонента, выполняющего вызов useContext().
React автоматически перерисовывает все дочерние компоненты, использующие определенный контекст, начиная с провайдера, получившего другое значение. Предыдущее и последующее значения сравниваются с помощью сравнения Object.is. Пропуск повторных рендеров с помощью memo не мешает дочерним компонентам получать свежие значения контекста.
Если ваша система сборки выдает дубликаты модулей на выходе (что может произойти при использовании симлинков), это может нарушить контекст. Передача чего-либо через контекст работает только в том случае, если SomeContext, который вы используете для предоставления контекста, и SomeContext, который вы используете для его чтения, являются точно одним и тем же объектом, что определяется сравнением ===.
useContext возвращает значение контекста для контекста, который вы передали. Чтобы определить значение контекста, React просматривает дерево компонентов и находит ближайшего поставщика контекста выше для данного контекста.
Чтобы передать контекст Button, оберните его или один из его родительских компонентов в соответствующий провайдер контекста:
Не имеет значения, сколько слоев компонентов находится между провайдером и Button. Когда Buttonв любом месте внутри Form вызывает useContext(ThemeContext), он получит "dark" в качестве значения.
useContext() всегда ищет ближайшего провайдера выше компонента, который его вызывает. Она ищет вверх и не рассматривает провайдеров в компоненте, из которого вы вызываете useContext().
Часто требуется, чтобы контекст менялся с течением времени. Чтобы обновить контекст, объедините его с state. Объявите переменную state в родительском компоненте и передайте текущее состояние как значение контекста провайдеру.
Теперь любой Button внутри провайдера будет получать текущее значение theme. Если вы вызовете setTheme для обновления значения темы, которое вы передаете провайдеру, все компоненты Button будут перерисованы с новым значением 'light'.
В этом примере компонент MyApp хранит переменную состояния, которая затем передается провайдеру ThemeContext. Установка флажка "Темный режим" обновляет состояние. Изменение предоставленного значения пересматривает все компоненты, использующие данный контекст.
Обратите внимание, что value="dark" передает строку "dark", а value={theme} передает значение переменной JavaScript theme с фигурными скобками JSX. Фигурные скобки также позволяют передавать контекстные значения, которые не являются строками.
В этом примере есть переменная состояния currentUser, которая содержит объект. Вы объединяете { currentUser, setCurrentUser } в один объект и передаете его вниз через контекст внутри value={}. Это позволяет любому компоненту ниже, например, LoginButton, читать и currentUser, и setCurrentUser, а затем вызывать setCurrentUser, когда это необходимо.
В этом примере есть два независимых контекста. ThemeContext предоставляет текущую тему, которая является строкой, а CurrentUserContext содержит объект, представляющий текущего пользователя.
По мере роста вашего приложения ожидается, что у вас будет "пирамида" контекстов ближе к корню вашего приложения. В этом нет ничего плохого. Однако, если вам эстетически не нравится вложенность, вы можете извлечь провайдеров в один компонент. В этом примере MyProviders скрывает "сантехнику" и отображает переданные ему дочерние элементы внутри необходимых провайдеров. Обратите внимание, что состояния theme и setTheme нужны в самом MyApp, поэтому MyApp по-прежнему владеет этой частью состояния.
В больших приложениях часто используется сочетание контекста с reducer для извлечения логики, связанной с некоторым состоянием, из компонентов. В этом примере вся "проводка" спрятана в TasksContext.js, который содержит редуктор и два отдельных контекста.
Если React не может найти поставщиков данного контекста в родительском дереве, значение контекста, возвращаемое useContext(), будет равно значению по умолчанию, которое вы указали при создании контекста:
1
constThemeContext=createContext(null);
Значение по умолчанию никогда не изменяется. Если вы хотите обновить контекст, используйте его вместе с state, как описано выше.
Часто вместо null можно использовать какое-то более значимое значение по умолчанию, например:
1
constThemeContext=createContext('light');
Таким образом, если вы случайно отобразите какой-либо компонент без соответствующего провайдера, он не сломается. Это также поможет вашим компонентам хорошо работать в тестовой среде без установки большого количества провайдеров в тестах.
В примере ниже кнопка "Toggle theme" всегда светлая, потому что она находится вне любого провайдера контекста темы, а значение контекстной темы по умолчанию - 'light'. Попробуйте изменить тему по умолчанию на 'dark'.
Вы можете "накапливать" информацию при вложении провайдеров контекста. В этом примере компонент Section отслеживает LevelContext, который определяет глубину вложенности раздела. Он считывает LevelContext из родительской секции и предоставляет своим дочерним секциям номер LevelContext, увеличенный на единицу. В результате компонент Heading может автоматически решать, какой из тегов <h1>, <h2>, <h3>, ..., использовать, основываясь на том, сколько компонентов Section вложено в него.
import{useContext}from'react';import{LevelContext}from'./LevelContext.js';exportdefaultfunctionHeading({children}){constlevel=useContext(LevelContext);switch(level){case0:throwError('Heading must be inside a Section!');case1:return<h1>{children}</h1>;case2:return<h2>{children}</h2>;case3:return<h3>{children}</h3>;case4:return<h4>{children}</h4>;case5:return<h5>{children}</h5>;case6:return<h6>{children}</h6>;default:throwError('Unknown level: '+level);}}
Здесь контекстное значение - это объект JavaScript с двумя свойствами, одно из которых - функция. Всякий раз, когда MyApp перерисовывается (например, при обновлении маршрута), это будет разный объект, указывающий на разную функцию, поэтому React также должен будет перерисовать все компоненты в глубине дерева, которые вызывают useContext(AuthContext).
В небольших приложениях это не является проблемой. Однако нет необходимости перерисовывать их, если базовые данные, такие как currentUser, не изменились. Чтобы помочь React воспользоваться этим фактом, вы можете обернуть функцию login в useCallback и обернуть создание объекта в useMemo. Это оптимизация производительности:
В результате этого изменения, даже если MyApp потребуется перерендеринг, компонентам, вызывающим useContext(AuthContext), не потребуется перерендеринг, если только currentUser не изменился.
Мой компонент не видит значение от моего провайдера¶
Есть несколько распространенных способов, как это может произойти:
Вы отображаете <SomeContext.Provider> в том же компоненте (или ниже), где вы вызываете useContext(). Переместите <SomeContext.Provider>выше и вне компонента, вызывающего useContext().
Возможно, вы забыли обернуть ваш компонент <SomeContext.Provider>, или вы поместили его в другую часть дерева, чем думали. Проверьте правильность иерархии с помощью React DevTools.
Возможно, вы столкнулись с какой-то проблемой сборки с вашим инструментарием, из-за которой SomeContext, как видно из предоставляющего компонента, и SomeContext, как видно из читающего компонента, являются двумя разными объектами. Это может произойти, например, если вы используете симлинки. Вы можете проверить это, присвоив им глобальные значения window.SomeContext1 и window.SomeContext2, а затем проверив в консоли, равно ли window.SomeContext1 === window.SomeContext2. Если они не одинаковы, исправьте эту проблему на уровне инструмента сборки.
Я всегда получаю undefined из моего контекста, хотя значение по умолчанию другое¶
У вас может быть провайдер без value в дереве:
1234
// 🚩 Doesn't work: no value prop<ThemeContext.Provider><Button/></ThemeContext.Provider>
Если вы забыли указать value, это все равно, что передать value={undefined}.
Вы также могли по ошибке использовать другое имя пропса:
1234
// 🚩 Doesn't work: prop should be called "value"<ThemeContext.Providertheme={theme}><Button/></ThemeContext.Provider>
В обоих этих случаях вы должны увидеть предупреждение от React в консоли. Чтобы исправить их, вызовите свойство value:
1234
// ✅ Passing the value prop<ThemeContext.Providervalue={theme}><Button/></ThemeContext.Provider>
Обратите внимание, что значение по умолчанию из вашего вызова createContext(defaultValue) используется только если выше вообще нет подходящего провайдера. Если где-то в родительском дереве есть компонент <SomeContext.Provider value={undefined}>, компонент, вызывающий useContext(SomeContext) получит undefined в качестве значения контекста.