Перейти к содержимому

Для чего нужен context react

  • автор:

Руководство по React Context и useContext ()

Перевод статьи
Контекст React предоставляет данные компонентам независимо от того, насколько глубоко они находятся в дереве компонентов.

В этом посте вы узнаете, как использовать концепцию контекста в React.

  1. Как использовать контекст
  2. Когда он вам пригодится?
  3. Пример использования (имя пользователя)
  4. Контекст на помощь
  5. Когда контекст меняется
  6. Обновление контекста
  7. Вывод

1 Как использовать контекст

Для использования контекста в React требуется 3 простых шага: создание контекста(A), предоставление контекста(Б) и использование контекста(В).

A.Создадим контекст
Встроенная фабричная функция createContext (по умолчанию) создает экземпляр контекста

Заводская функция принимает один необязательный аргумент: ‘Любое значение’

Б. Предоставление контекста

Компонент Context.Provider — доступный в экземпляре контекста, используется для предоставления контекста его дочерним компонентам, независимо от их глубины.

Чтобы установить значение контекста, используйте свойство value, доступное в />

Опять же, здесь важно то, что все компоненты, которые позже захотят использовать контекст, должны быть заключены в компонент провайдера.

В. Использование контекста

Использование контекста может быть выполнено двумя способами. Первый способ — это использовать хук useContext (Context)

Хук возвращает значение контекста: value = useContext (Context)

Хук также гарантирует повторный рендеринг компонента при изменении значения контекста

Второй способ — использовать функцию рендеринга, предоставленную в качестве дочернего для специального компонента Context.Consumer, доступного в экземпляре контекста:

Опять же, в случае, если значение контекста изменится — повторно отрендерит свою функцию рендеринга.

Вы можете иметь столько дочерних компонентов, сколько хотите для одного контекста. Если значение контекста изменяется (путем изменения свойства value провайдера (), то все дочерние компоненты немедленно уведомляются и повторно обрабатываются. Если дочерний компонент не заключен внутри провайдера, но все же пытается получить доступ к значению контекста (используя useContext (Context) или) , то значение контекста будет аргументом значения по умолчанию, предоставленным createContext (defaultValue) фабричная функция, создавшая контекст.

2 Когда он вам пригодится?

Основная идея использования контекста — предоставить вашим компонентам доступ к некоторым глобальным данным и повторный рендеринг при изменении этих глобальных данных. Контекст решает проблему сверления реквизита: когда вам нужно передать реквизиты от родителей детям.

Вы можете держать внутри контекста:

  • Глобальное состояние приложения
  • Тема приложения
  • Конфигурация приложения
  • Аутентификация пользователя
  • Пользовательские настройки
  • Предпочтительный язык
  • Набор услуг

С другой стороны, вы должны хорошо подумать, прежде чем принимать решение об использовании контекста в своем приложении.

  • Во-первых, интеграция контекста добавляет сложности. Создание контекста, обертывание всего в провайдере, использование useContext () в каждом дочернем компоненте — это увеличивает сложность.
  • Во-вторых, добавление контекста затрудняет модульное тестирование компонентов. Во время модульного тестирования вам придется заключить потребительские компоненты в поставщик контекста. Включая компоненты, на которые косвенно влияет контекст — родителей дочерних компонентов контекста!

3 Пример использования (имя пользователя)

Самый простой способ передать данные от родителя к дочернему компоненту — это когда родитель назначает реквизиты своему дочернему компоненту

т.к. пишу на TS — Props обьявил ранее и назначил тип имени — строкой

Родительский компонент назначает данные userName своему дочернему компоненту с помощью свойства userName.

Это обычный способ передачи данных с использованием реквизита. Вы можете использовать этот подход без проблем. Ситуация меняется, когда дочерний компонент не является прямым потомком , но содержится в нескольких предках. Например, предположим, что компонент (тот, у которого есть глобальные данные userName) отображает компонент , который, в свою очередь, отображает компонент

, который, в свою очередь, в конечном итоге отображает компонент (который хотел бы получить доступ к имени пользователя).

Посмотрим как это будет выглядеть

Вы можете видеть проблему: поскольку компонент отображается глубоко в дереве, и все родительские компоненты ( и

) должны передавать свойство userName.

Эта проблема также известна как Буровая установка.

Контекст React — возможное решение. Давайте посмотрим, как его применить в следующем разделе.

4 Контекст на помощь

Напоминаем, что для применения контекста React требуются 3 участника: контекст, провайдер, извлеченный из контекста, и дочерний компонент.

Вот как будет выглядеть пример приложения при применении к нему контекста

Во-первых, const UserContext = createContext (‘Unknown’) создает контекст, в котором будет храниться информация об имени пользователя.

Во-вторых, внутри компонента дочерние компоненты приложения заключены в контекст пользователя: . Обратите внимание, что свойство value компонента поставщика важно: именно так вы устанавливаете значение контекста.

Наконец, становится потребителем контекста с помощью встроенной ловушки useContext (UserContext). Хук вызывается с контекстом в качестве аргумента и возвращает значение имени пользователя.

Промежуточные компоненты и

не должны передавать свойство userName. В этом большое преимущество контекста — он снимает бремя передачи данных через промежуточные компоненты.

5 Когда контекст меняется

Когда значение контекста изменяется путем изменения свойства значения поставщика контекста (), тогда все его потребители уведомляются и повторно обрабатываются. Например, если я изменю имя пользователя с «Дэн Абрамов» на «Дэн, Дэн Абрамов», то потребитель немедленно выполнит повторный рендеринг, чтобы отобразить последнее значение контекста

6 Обновление контекста

React Context API по умолчанию не имеет состояния и не предоставляет специальный метод для обновления значения контекста из компонентов-потребителей.

Но это можно легко реализовать, интегрировав механизм управления состоянием (например, хуки useState () или useReducer ()) и предоставив функцию обновления прямо в контексте рядом с самим значением. В следующем примере компонент использует ловушку useState () для управления значением контекста.

потребитель считывает значение контекста, из которого извлекаются userName и setUserName. Затем потребитель может обновить значение контекста, вызвав функцию обновления setUserName (newContextValue)

— еще один потребитель контекста. Когда обновляет контекст, этот компонент также обновляется.

Обратите внимание, что запоминает значение контекста. Мемоизация сохраняет объект значения контекста одним и тем же до тех пор, пока userName остается неизменным, предотвращая повторную визуализацию потребителей каждый раз, когда повторно отображается . В противном случае, без мемоизации, const value = создаст разные экземпляры объекта во время повторного рендеринга , инициируя повторный рендеринг в потребителях контекста.

Контекст в React — это концепция, которая позволяет вам снабжать дочерние компоненты глобальными данными, независимо от того, насколько глубоко они находятся в дереве компонентов.

Для использования контекста требуется 3 шага: создание, предоставление и использование контекста. При интеграции контекста в ваше приложение учтите, что он значительно усложняет его. Иногда «прокинуть» пропсы через 2-3 уровня иерархии не составляет большой проблемы.

Полное руководство по React Context

Представленные советы будут полезны даже тем, кто еще никогда не работал с React. Возможности Context изучим на простых, пошаговых примерах.

Что такое React Context?

React Context позволяет без использования props’ов передавать и применять (потреблять) данные в любом компоненте приложения.

Другими словами, React Context упрощает обмен данными (состоянием) между компонентами приложений.

Когда нужен React Context?

React Context эффективен при передаче данных, используемых в любом компоненте приложения.

Возможные типы данных:

· Данные темы (например, темный или светлый режим);

· Пользовательские данные прошедшего аутентификацию пользователя;

· Зависящие от местоположения данные (например, язык или регион пользователя).

В React Context следует размещать данные, не требующие частого обновления.

Причина в том, что Context предназначен лишь для упрощения процесса использования данных. Он не является целостной системой управления состоянием.

Сontext можно рассматривать как эквивалент глобальных переменных для компонентов React.

Какие проблемы решает React Context?

React Context помогает отказаться от “пробрасывания” props’ов (props drilling).

Термин Props drilling означает передачу props’ов во вложенный компонент через другие компоненты, расположенные ниже родительского и сами в данный момент не нуждающиеся этих пропсах.

Рассмотрим пример props drilling. В этом приложении есть доступ к данным темы, которые мы хотим передать как prop всем компонентам приложения.

При этом прямые дочерние элементы App , такие как Header , также должны передавать с помощью props’ов вниз по иерархии данные темы (theme data).

export default function App(< theme >) return ( 
<>
/>
/>
/>
/>

);
>
function Header(< theme >) return (
<>
/>
/>
/>

);
>

В чем здесь проблема?

В том, что theme пробрасывается через несколько компонентов, в которых этот prop в данный момент вовсе не нужен.

В Header данные theme нужны лишь для того, чтобы передать их вниз дочернему компоненту. То есть, для User , Login и Menu было бы лучше напрямую получать данные theme .

Отказаться от пробрасывания пропсов позволяет React Context.

Как использовать React Context?

Context — это API, встроенное в React с версии 16.

Поэтому создавать и использовать Context можно посредством импорта React в созданные на его основе проекты.

4 шага использования React Context:

1. Создать Context, используя метод createContext .

2. Использовать поставщика созданного Context (context provider) в качестве оболочки дерева компонентов.

3. Поместить любое необходимое значение в context provider, используя prop value .

4. Прочесть это значение в любом компоненте с помощью context consumer.

Звучит несколько запутанно? Но здесь нет ничего сложного.

Вот простой пример. В рассмотренном ранее App передадим с помощью Context имя Reed и прочитаем его во вложенном компоненте User .

import React from 'react';export const UserContext = React.createContext();export default function App() return ( 



)
>
function User() return (

>


)
>

Разберем этот код по шагам.

1. В компоненте App создаем Context с помощью React.create Context () и помещаем результат в переменную UserContext . Его практически всегда можно экспортировать (как сделано здесь), потому что ваш компонент будет в другом файле. Обратим внимание, при вызове React.createContext() можно передать начальное значение в prop value .

2. В компоненте App используем User Context . В данном случае User Context.Provider . Созданный Context — объект с двумя свойствами: Provider и Consumer . Они являются компонентами. Чтобы передать value вниз каждому компоненту App, используем в качестве оболочки компонент поставщика (в данном случае User ).

3. В UserContext.Provider помещаем значение, которое хотим передать вниз по иерархии компонентов. Для этого устанавливаем его равным prop value . В данном случае это имя Reed.

4. Используем потребительский компонент в UserContext.ConsumerUser и везде, где хотим потреблять (использовать) представленные в контексте данные. Для использования переданного вниз значения применяем render props pattern. Это просто функция, которую потребительский компонент (consumer component) предоставляет нам в качестве prop. Данная функция возвращает используемое в дальнейшем value .

Что такое хук useContext?

В приведенном выше примере не очень понятным может быть шаблон рендеринга props’ов для потребления контекста.

В React версии 16.8 для этого есть другой способ с применением хуков (с помощью useContext hook).

Вместо использования render props можно передать весь объект-контекст в React.useContext() для потребления в верхней части компонента.

Пример использования хука useContext hook:

import React from 'react';export const UserContext = React.createContext();export default function App() return ( 



)
>
function User() const value = React.useContext(UserContext);

return ;
>

Преимущество хука useContext в том, что он делает компоненты более компактными и позволяет создавать пользовательские хуки.

В зависимости от предпочитаемого шаблона (паттерна) можно использовать напрямую потребительский компонент или хук useContext.

Всегда ли нужен Context?

Ошибкой многих разработчиков является обращение к Context , когда им нужно передавать props’ы на несколько уровней вниз к компоненту.

Вот приложение с вложенным компонентом Avatar , которому требуется два реквизита username и avatarSrc из компонента App .

export default function App(< user >) const < username, avatarSrc >= user; return ( 

avatarSrc= />

);
>
function Navbar(< username, avatarSrc >) return (

);
>
function Avatar(< username, avatarSrc >) return ;
>

Нужно по возможности избегать передачи нескольких props’ов через компоненты, которым они не требуются.

Как это сделать?

Вначале следует упорядочить компоненты вместо того, чтобы сразу обращаться к контексту для передачи props’ов.

Поскольку только самый верхний компонент App должен знать о компоненте Avatar , мы можем создать его непосредственно в App .

Это позволяет передавать один prop avatar вместо двух.

export default function App(< user >) const < username, avatarSrc >= user; const avatar = alt= />; return ( 

/>

);
>
function Navbar(< avatar >) return ;
>

Короче говоря, не следует сразу обращаться к Context. Нужно посмотреть, нельзя ли лучше организовать свои компоненты без props’ов.

Может ли React Context заменить Redux?

Нельзя ответить однозначно.

Redux — более простой способ передачи данных для изучающих React. Потому что в Redux имеется React Context.

Однако, если состояние не обновлять, а просто передавать по дереву компонентов, то и не нужен Redux (универсальная библиотека управления состоянием).

Ограничения применимости React Context

Почему невозможно обновить значение, которое передается вниз с помощью React Сontext?

Комбинируя React Сontext с хуком useReducer можно создать импровизированную библиотеку управления состоянием без использования сторонней библиотеки. Но из соображений потери производительности практикуется это редко.

Проблема здесь в том, что React Context запускает повторный рендеринг.

Что произойдет, если передавать объект провайдеру React Context и обновлять в нем какое-либо свойство? В любом использующем этот контекст компоненте будет выполнен повторный рендеринг.

Подобные проблемы с производительностью не всегда возникают в небольших приложениях с несколькими не очень часто обновляемыми значениями состояния (например, данные темы). Но все меняется в приложении с множеством компонентов в иерархии, если часто обновлять состояния.

  • Как предотвратить состояние гонки с помощью React Context API
  • Современное приложение выбирает… Redux, Context или Recoil?
  • Лучший способ для привязки обработчиков событий в React

Контекст

Контекст позволяет передавать данные через дерево компонентов без необходимости передавать пропсы на промежуточных уровнях.

В типичном React-приложении данные передаются сверху вниз (от родителя к дочернему компоненту) с помощью пропсов. Однако, подобный способ использования может быть чересчур громоздким для некоторых типов пропсов (например, выбранный язык, UI-тема), которые необходимо передавать во многие компоненты в приложении. Контекст предоставляет способ делиться такими данными между компонентами без необходимости явно передавать пропсы через каждый уровень дерева.

  • Когда использовать контекст
  • Перед тем, как вы начнёте использовать контекст
  • API
    • React.createContext
    • Context.Provider
    • Class.contextType
    • Context.Consumer
    • Context.displayName
    • Динамический контекст
    • Изменение контекста из вложенного компонента
    • Использование нескольких контекстов

    Когда использовать контекст

    Контекст разработан для передачи данных, которые можно назвать «глобальными» для всего дерева React-компонентов (например, текущий аутентифицированный пользователь, UI-тема или выбранный язык). В примере ниже мы вручную передаём проп theme , чтобы стилизовать компонент Button:

    class App extends React.Component  render()  return Toolbar theme="dark" />; > > function Toolbar(props)   // Компонент Toolbar должен передать проп "theme" ниже, // фактически не используя его. Учитывая, что у вас в приложении // могут быть десятки компонентов, использующих UI-тему, // вам придётся передавать проп "theme" через все компоненты. // И в какой-то момент это станет большой проблемой. return ( div> ThemedButton theme=props.theme> />  div> ); > class ThemedButton extends React.Component  render()  return Button theme=this.props.theme> />; > >

    Контекст позволяет избежать передачи пропсов в промежуточные компоненты:

    // Контекст позволяет передавать значение глубоко// в дерево компонентов без явной передачи пропсов// на каждом уровне. Создадим контекст для текущей// UI-темы (со значением "light" по умолчанию).const ThemeContext = React.createContext('light'); class App extends React.Component  render()   // Компонент Provider используется для передачи текущей // UI-темы вниз по дереву. Любой компонент может использовать // этот контекст и не важно, как глубоко он находится. // В этом примере мы передаём "dark" в качестве значения контекста. return ( ThemeContext.Provider value="dark">  Toolbar /> ThemeContext.Provider> ); > > // Компонент, который находится в середине,// больше не должен явно передавать тему вниз.function Toolbar()  return ( div> ThemedButton /> div> ); > class ThemedButton extends React.Component   // Определяем contextType, чтобы получить значение контекста. // React найдёт (выше по дереву) ближайший Provider-компонент, // предоставляющий этот контекст, и использует его значение. // В этом примере значение UI-темы будет "dark". static contextType = ThemeContext; render()   return Button theme=this.context> />; > >

    Перед тем, как вы начнёте использовать контекст

    Обычно контекст используется, если необходимо обеспечить доступ данных во многих компонентах на разных уровнях вложенности. По возможности не используйте его, так как это усложняет повторное использование компонентов.

    Если вы хотите избавиться от передачи некоторых пропсов на множество уровней вниз, обычно композиция компонентов является более простым решением, чем контекст.

    Например, давайте рассмотрим компонент Page , который передаёт пропсы user и avatarSize на несколько уровней вниз, чтобы глубоко вложенные компоненты Link и Avatar смогли их использовать:

    Page user=user> avatarSize=avatarSize> /> // . который рендерит . PageLayout user=user> avatarSize=avatarSize> /> // . который рендерит . NavigationBar user=user> avatarSize=avatarSize> /> // . который рендерит . Link href=user.permalink>> Avatar user=user> size=avatarSize> /> Link>

    Передача пропсов user и avatarSize вниз выглядит избыточной, если в итоге их использует только компонент Avatar . Так же плохо, если компоненту Avatar вдруг потребуется больше пропсов сверху, тогда вам придётся добавить их на все промежуточные уровни.

    Один из способов решить эту проблему без контекста — передать вниз сам компонент Avatar , в случае чего промежуточным компонентам не нужно знать о пропсах user и avatarSize :

    function Page(props)  const user = props.user; const userLink = ( Link href=user.permalink>> Avatar user=user> size=props.avatarSize> /> Link> ); return PageLayout userLink=userLink> />; > // Теперь, это выглядит так: Page user=user> avatarSize=avatarSize>/> // . который рендерит . PageLayout userLink=. > /> // . который рендерит . NavigationBar userLink=. > /> // . который рендерит . props.userLink>

    С этими изменениями, только корневой компонент Page знает о том, что компоненты Link и Avatar используют user и avatarSize .

    Инверсия управления может сделать ваш код чище во многих случаях, уменьшая количество пропсов, которые вы должны передавать через ваше приложение, и давая больше контроля корневым компонентам. Однако, такое решение не всегда подходит. Перемещая больше сложной логики вверх по дереву, вы перегружаете вышестоящие компоненты.

    Вы не ограничены в передаче строго одного компонента. Вы можете передать несколько дочерних компонентов или, даже, создать для них разные «слоты», как показано здесь:

    function Page(props)  const user = props.user; const content = Feed user=user> />; const topBar = ( NavigationBar> Link href=user.permalink>> Avatar user=user> size=props.avatarSize> /> Link> NavigationBar> ); return ( PageLayout topBar=topBar> content=content> /> ); >

    Этого паттерна достаточно для большинства случаев, когда вам необходимо отделить дочерний компонент от его промежуточных родителей. Вы можете пойти ещё дальше, используя рендер-пропсы, если дочерним компонентам необходимо взаимодействовать с родителем перед рендером.

    Однако, иногда одни и те же данные должны быть доступны во многих компонентах на разных уровнях дерева и вложенности. Контекст позволяет распространить эти данные и их изменения на все компоненты ниже по дереву. Управление текущим языком, UI темой или кешем данных — это пример тех случаев, когда реализация с помощью контекста будет проще использования альтернативных подходов.

    const MyContext = React.createContext(defaultValue);

    Создаёт объект Context . Когда React рендерит компонент, который подписан на этот объект, React получит текущее значение контекста из ближайшего подходящего Provider выше в дереве компонентов.

    Аргумент defaultValue используется только в том случае, если для компонента нет подходящего Provider выше в дереве. Значение по умолчанию может быть полезно для тестирования компонентов в изоляции без необходимости оборачивать их. Обратите внимание: если передать undefined как значение Provider , компоненты, использующие этот контекст, не будут использовать defaultValue .

    MyContext.Provider value=/* некоторое значение */>>

    Каждый объект Context используется вместе с Provider компонентом, который позволяет дочерним компонентам, использующим этот контекст, подписаться на его изменения.

    Компонент Provider принимает проп value , который будет передан во все компоненты, использующие этот контекст и являющиеся потомками этого компонента Provider . Один Provider может быть связан с несколькими компонентами, потребляющими контекст. Так же компоненты Provider могут быть вложены друг в друга, переопределяя значение контекста глубже в дереве.

    Все потребители, которые являются потомками Provider, будут повторно рендериться, как только проп value у Provider изменится. Потребитель (включая .contextType и useContext ) перерендерится при изменении контекста, даже если его родитель, не использующий данный контекст, блокирует повторные рендеры с помощью shouldComponentUpdate .

    Изменения определяются с помощью сравнения нового и старого значения, используя алгоритм, аналогичный Object.is .

    Примечание

    Способ, по которому определяются изменения, может вызвать проблемы при передаче объекта в value : смотрите Предостережения.

    class MyClass extends React.Component  componentDidMount()  let value = this.context; /* выполнить побочный эффект на этапе монтирования, используя значение MyContext */ > componentDidUpdate()  let value = this.context; /* . */ > componentWillUnmount()  let value = this.context; /* . */ > render()  let value = this.context; /* отрендерить что-то, используя значение MyContext */ > > MyClass.contextType = MyContext;

    В свойство класса contextType может быть назначен объект контекста, созданный с помощью React.createContext() . С помощью этого свойства вы можете использовать ближайшее и актуальное значение указанного контекста при помощи this.context . В этом случае вы получаете доступ к контексту, как во всех методах жизненного цикла, так и в рендер-методе.

    Примечание

    Вы можете подписаться только на один контекст, используя этот API. В случае, если вам необходимо использовать больше одного, смотрите Использование нескольких контекстов.

    Если вы используете экспериментальный синтаксис публичных полей класса, вы можете использовать static поле класса, чтобы инициализировать ваш contextType .

    class MyClass extends React.Component  static contextType = MyContext; render()  let value = this.context; /* отрендерить что-то, используя значение MyContext */ > >
    MyContext.Consumer> value => /* отрендерить что-то, используя значение контекста */> MyContext.Consumer>

    Consumer — это React-компонент, который подписывается на изменения контекста. В свою очередь, использование этого компонента позволяет вам подписаться на контекст в функциональном компоненте.

    Consumer принимает функцию в качестве дочернего компонента. Эта функция принимает текущее значение контекста и возвращает React-компонент. Передаваемый аргумент value будет равен ближайшему (вверх по дереву) значению этого контекста, а именно пропу value компонента Provider . Если такого компонента Provider не существует, аргумент value будет равен значению defaultValue , которое было передано в createContext() .

    Примечание

    Подробнее про паттерн «функция как дочерний компонент» можно узнать на странице Рендер-пропсы.

    Объекту Context можно задать строковое свойство displayName . React DevTools использует это свойство при отображении контекста.

    К примеру, следующий компонент будет отображаться под именем MyDisplayName в DevTools:

    const MyContext = React.createContext(/* некоторое значение */); MyContext.displayName = 'MyDisplayName'; MyContext.Provider> // "MyDisplayName.Provider" в DevTools MyContext.Consumer> // "MyDisplayName.Consumer" в DevTools

    Более сложный пример динамических значений для UI-темы:

    theme-context.js

    export const themes =  light:  foreground: '#000000', background: '#eeeeee', >, dark:  foreground: '#ffffff', background: '#222222', >, >; export const ThemeContext = React.createContext( themes.dark // значение по умолчанию);

    themed-button.js

    import ThemeContext> from './theme-context'; class ThemedButton extends React.Component  render()  let props = this.props; let theme = this.context; return ( button . props> style=backgroundColor: theme.background>> /> ); > > ThemedButton.contextType = ThemeContext; export default ThemedButton;

    app.js

    import ThemeContext, themes> from './theme-context'; import ThemedButton from './themed-button'; // Промежуточный компонент, который использует ThemedButton function Toolbar(props)  return ( ThemedButton onClick=props.changeTheme>> Change Theme ThemedButton> ); > class App extends React.Component  constructor(props)  super(props); this.state =  theme: themes.light, >; this.toggleTheme = () =>  this.setState(state => ( theme: state.theme === themes.dark ? themes.light : themes.dark, >)); >; > render()   // ThemedButton внутри ThemeProvider использует // значение светлой UI-темы из состояния, в то время как // ThemedButton, который находится вне ThemeProvider, // использует тёмную UI-тему из значения по умолчанию return ( Page> ThemeContext.Provider value=this.state.theme>>  Toolbar changeTheme=this.toggleTheme> />  ThemeContext.Provider>  Section> ThemedButton />  Section> Page> ); > > const root = ReactDOM.createRoot( document.getElementById('root') ); root.render(App />);

    Изменение контекста из вложенного компонента

    Довольно часто необходимо изменить контекст из компонента, который находится где-то глубоко в дереве компонентов. В этом случае вы можете добавить в контекст функцию, которая позволит потребителям изменить значение этого контекста:

    theme-context.js

    // Убедитесь, что форма значения по умолчанию, // передаваемого в createContext, совпадает с формой объекта, // которую ожидают потребители контекста. export const ThemeContext = React.createContext(  theme: themes.dark, toggleTheme: () => >,>);

    theme-toggler-button.js

    import ThemeContext> from './theme-context'; function ThemeTogglerButton()   // ThemeTogglerButton получает из контекста // не только значение UI-темы, но и функцию toggleTheme. return ( ThemeContext.Consumer> (theme, toggleTheme>) => ( button onClick=toggleTheme> style=backgroundColor: theme.background>>> Toggle Theme button> )> ThemeContext.Consumer> ); > export default ThemeTogglerButton;

    app.js

    import ThemeContext, themes> from './theme-context'; import ThemeTogglerButton from './theme-toggler-button'; class App extends React.Component  constructor(props)  super(props); this.toggleTheme = () =>  this.setState(state => ( theme: state.theme === themes.dark ? themes.light : themes.dark, >)); >; // Состояние хранит функцию для обновления контекста, // которая будет также передана в Provider-компонент. this.state =  theme: themes.light, toggleTheme: this.toggleTheme, >; > render()   // Всё состояние передаётся в качестве значения контекста return ( ThemeContext.Provider value=this.state>>  Content /> ThemeContext.Provider> ); > > function Content()  return ( div> ThemeTogglerButton /> div> ); > const root = ReactDOM.createRoot( document.getElementById('root') ); root.render(App />);

    Использование нескольких контекстов

    Чтобы последующие рендеры (связанные с контекстом) были быстрыми, React делает каждого потребителя контекста отдельным компонентом в дереве.

    // Контекст UI-темы, со светлым значением по умолчанию const ThemeContext = React.createContext('light'); // Контекст активного пользователя const UserContext = React.createContext( name: 'Guest', >); class App extends React.Component  render()  const signedInUser, theme> = this.props; // Компонент App, который предоставляет начальные значения контекстов return ( ThemeContext.Provider value=theme>>  UserContext.Provider value=signedInUser>>  Layout /> UserContext.Provider>  ThemeContext.Provider> ); > > function Layout()  return ( div> Sidebar /> Content /> div> ); > // Компонент, который может использовать несколько контекстов function Content()  return ( ThemeContext.Consumer>  theme => ( UserContext.Consumer>  user => ( ProfilePage user=user> theme=theme> /> )>  UserContext.Consumer> )>  ThemeContext.Consumer> ); >

    Если два или более значений контекста часто используются вместе, возможно, вам стоит рассмотреть создание отдельного компонента, который будет передавать оба значения дочерним компонентам с помощью паттерна «рендер-пропс».

    Контекст использует сравнение по ссылкам, чтобы определить, когда запускать последующий рендер. Из-за этого существуют некоторые подводные камни, например, случайные повторные рендеры потребителей, при перерендере родителя Provider-компонента. В следующем примере будет происходить повторный рендер потребителя каждый повторный рендер Provider-компонента, потому что новый объект, передаваемый в value , будет создаваться каждый раз:

    class App extends React.Component  render()  return ( MyContext.Provider value=something: 'something'>>>  Toolbar /> MyContext.Provider> ); > >

    Один из вариантов решения этой проблемы — хранение этого объекта в состоянии родительского компонента:

    class App extends React.Component  constructor(props)  super(props); this.state =   value: something: 'something'>, >; > render()  return ( MyContext.Provider value=this.state.value>>  Toolbar /> MyContext.Provider> ); > >

    Примечание

    В прошлом React имел только экспериментальный API контекста. Старый API будет поддерживаться во всех 16.x релизах, но использующие его приложения должны перейти на новую версию. Устаревший API будет удалён в будущем крупном релизе React. Вы можете прочитать документацию устаревшего контекста здесь.

    Контекст (Context API) — JS: React

    Передача данных через пропсы вниз по иерархии компонентов — это немного многословный, но простой механизм. Всегда видно, откуда пришли данные и как они попали внутрь, а компоненты легко переиспользовать, так как они зависят только от входных данных. Но бывают ситуации, когда передача пропсов не вписывается в то, как работает код.

    Возьмем для примера текущего пользователя. Часто данные пользователя нужны одновременно в разных частях страницы, причем в очень глубоких компонентах. Для этого придется передавать пользователя буквально по всей иерархии: даже там, где он не нужен компоненту. Единственная цель такой передачи — прокинуть данные до места назначения, пройдя по пути все промежуточные компоненты. Получается, что множество компонентов никак не используют пользователя, они просто передают их дальше по цепочке. В нашей ситуации данные пользователя глобальные. Они нужны сразу многим компонентам на разных уровнях иерархии. Для таких задач в React существует обходной путь.

    Context API — механизм, позволяющий сделать глобальные данные доступными из любого компонента напрямую, без прокидывания пропсов. Его использование сводится к трем шагам:

    // В параметр передается значение по умолчанию // Здесь передаем пустой объект, потому что пользователя еще нет, // но он будет (и будет объектом) // Контекст может хранить только одно значение // Имя контекста выбирается исходя из того, какие внутри хранятся данные const UserContext = React.createContext(<>); 
    // Контекст будет доступен только внутри тех компонентов, которые он оборачивает // и в тех, что вложены в данные компоненты // currentUser — данные текущего пользователя UserContext.Provider value=currentUser>> App /> UserContext.Provider> 
    import UserContext from '. '; // Любой компонент внутри блока class InnerComponent extends React.Component  // Определяем тип контекста static contextType = UserContext; render()  // Получаем доступ к контексту через this.context return Profile user=this.context> />; > > 

    Еще один пример, где несколько компонентов используют данные из контекста:

    // Создаем контекст const CompanyContext = React.createContext(<>); // Компонент адреса компании class CompanyAddressComponent extends React.Component  // Компонент использует контекст static contextType = CompanyContext; render()  // Извлекаем данные из контекста const  context > = this; const  address > = context; return ( <> address.street> br /> address.post> address.city> address.country>  ); > > // Другой компонент отрисовывает название компании class CompanyNameComponent extends React.Component  // Оба компонента используют один контекст static contextType = CompanyContext; render()  const  context > = this; const  name > = context; return <> name> ; > > class App extends React.Component  render()  // Компоненты могут быть вложены на любой глубине return ( <> CompanyNameComponent /> br /> CompanyAddressComponent />  ); > > const company =  name: 'Hexlet Ltd.', address:  street: 'Itälahdenkatu 22 A', post: '00210', city: 'Helsinki', country: 'Finland', >, >; const dom = ( CompanyContext.Provider value=company>> App /> CompanyContext.Provider> ); const mountNode = document.getElementById('react-root'); const root = ReactDOM.createRoot(mountNode); root.render(dom); 

    Открыть доступ

    Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

    • 130 курсов, 2000+ часов теории
    • 1000 практических заданий в браузере
    • 360 000 студентов

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *