Для чего нужен context react
Перейти к содержимому

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

  • автор:

React Context: создание глобального стора, используя useContext и useState

React Context API — это интерфейс, который позволяет сохранять некоторую величину (переменную или объект), и использовать ее между несколькими компонентами. Под самим же контекстным стором, или как его просто называют — контекстом, понимают эту сохраненную величину.

Интерфейс react-контекста состоит из метода createContext, компонента Context.Provider и хука useContext.

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

Для чего использовать контекст?

Цель создания контекста — это хранение и использование переменных, которые используются разными компонентами.

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

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

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

Давайте на примере создания стора, посмотрим как использовать react-контекст.

Создание провайдера

Для того чтобы создать хранилище — в корне проекта создадим папку contexts.

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

В нашем примере иерархия папок хранилища будет такая:

/contexts

/AppContext

AppContextProvider.jsx

AppContext.js

Провайдер мы не меняем и после объявления будем только импортировать в jsx. А работать мы будем с контекстным хранилищем — добавлять в него общие методы и переменные.

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

Итак, объявим Context:

const Context = React.createContext(null);

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

Давайте объявим провайдер и назовем его AppContextProvider:

export const AppContextProvider = (< children, . props >) => < const context = useCreateAppContext(props); return > ; >;

Здесь функция useCreateAppContext создает объект-хранилище.

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

Обратите внимание, что объект, возвращаемый useCreateAppContext, мы будем использовать в компонентах не на прямую, но получать его через useContext. Так величины контекстного хранилища будут находиться в памяти при ререндерах.

Так для получения контекстного хранилища, нужно использовать хук useContext с этим контекстом.

Давайте создадим в провайдере кастомный хук useAppContext, чтобы не экспортировать контекст и не передавать его каждый раз параметром из компонентов, в которых мы будем использовать стор:

export function useAppContext()

Теперь все вложенные в AppContextProvider компоненты могут получить контекстный стор, вызывая в себе метод useAppContext.

Последним для создания стора осталось объявить саму функцию useCreateAppContext, возвращающую объект который мы будем хранить в Provider value.

Исходные значения props — это те значения, которые получит компонент AppContextProvider.

Если мы хотим, чтобы компонент обновлялся при изменении глобальной переменной, переменная с сторе должна быть объявлена с помощью хука useState. Методы нужно оборачивать в useCallback.

Дальше содержимое стора можно наполнить чем угодно.

Давайте объявим такой стор:

export const useCreateAppContext = function(props) < const [test, setTest] = useState(props.test || 'Hello world'); const toggleTest = useCallback(() => < setTest(_test =>(_test === 'Hi' ? 'You are awesome' : 'Hi')); >); return < test, toggleTest, >; >

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

Мы можем задать исходное значение переменной test:

Затем, в компоненте MyComponent вызвать useAppContext:

const appContext = useAppContext();

Так как поля в контекстном хранилище, как и само хранилище, обернуты в хуки, то мы спокойно можем воспользоваться деструктуризацией, не боясь потерять контекст:

const = useAppContext();

И затем обращаться к нужным переменным:

console.log(test); toggleTest();

При изменении переменной test приведет к ререндеру компонента, тк в сторе она храниться в хуке useState.

Собственно, вот и вся магия.

Теперь содержимое useCreateAppContext можно менять на свое усмотрение и обращаться к нему глобально.

Контекстный стор позволяет вынести часть логики за пределы компонента, которую мы можем использовать в других местах. Это делает наш код “суше”. Также горизонтальная и восходящая передача данных между компонентами становиться намного проще.

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

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

Почитать подробнее про контекст можно в документации react.

Статья была изменена, принимая комментарии @KhodeN и @Alexandroppolus — для создания объекта, который храниться в контексте лучше использовать функцию-генератор, а не функцию-конструктор. Это дает более стандартизированную запись и защищает от возможных ошибок при вызове хуков из условия.

Что умеет Context API в ReactJS

В мире, где существует множество различных интерфейсных фреймворков, всегда трудно сделать выбор. Что лучше: использовать популярный Angular или выучить VueJS?

К счастью, сегодня у нас есть ReactJS, разработанный компанией Facebook фреймворк, который взял мир фронтенд фреймворков штурмом. React объединяет компоненты, виртуальный DOM и JSX и, похоже, удовлетворяет потребности очень многих разработчиков и охватывает широкую аудиторию.

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

Что ж, звучит здорово! Давайте разберемся, как это работает.

Свойства и состояние

В React есть две очень важные вещи, которые нужно понять – это свойства и состояние (props и state соответственно).

  • Свойства — это данные, которые передаются компоненту из родительского компонента.
  • Состояние — это данные, которыми управляет компонент. Если каждый компонент имеет свое собственное состояние, как мы можем поделиться этой информацией с другим компонентом? Тут можно использовать свойства, но их можно передавать только между родительским и дочерним компонентами.

Так что же делать, если у нас много уровней компонентов?

Пробрасывание и Context API

Пробрасывание (или prop-drilling) – это трудоемкий процесс, в рамках которого одни и те же данные отправляются почти на каждом уровне из-за требований финального уровня. Проще говоря, это явная передача значений через представление приложения.

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

const Lowest = (props) => ( 
) const Middle = (props) => (
/>
) class Highest extends Component < state = < name: "Context API" >render() < return
> >

Именование в этом примере условное, просто оно лучше демонстрирует способность контекста передаваться во вложенные компоненты. Более реалистичный сценарий может произойти в пользовательском интерфейсе: CardGrid -> CardContent -> CardFooter -> LikeButton.

Вернемся к нашему примеру. Вот так будут вложены компоненты Highest -> Middle -> Lowest:

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

Что ж, у нас есть React Context, который может позаботиться обо всей работе за нас.

Как работает Context API

React Context API предоставляет глобальное состояние для всего приложения.

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

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

Звучит невероятно! Что ж, давайте посмотрим, как мы могли бы применить этот API в простом приложении React.

Сборка простого приложения с Context API

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

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

Во-первых, для работы приложения понадобится зависимость react. Добавьте ее в свои зависимости. Если вы работаете в текстовом редакторе, выполните следующие шаги, чтобы установить его:

  • Установите npm глобально на свой компьютер, если он у вас еще не установлен:

npm install —save react

  • Проверьте файл package.json на наличие зависимости react.

Всякий раз, когда мы создаем приложение React, мы должны импортировать свои зависимости в главный файл .js, иначе он не будет знать, как их использовать. Итак, давайте импортируем то, что нам нужно, в верхней части файла index.js:

import React, < Component >from 'react';

Зависимости импортированы, теперь давайте перейдем к компоненту. Для удобочитаемости мы объявим контекст в переменной. Под импортом сделайте следующее:

const AppContext = React.createContext()

Уровни компонентов

Итак, компонент Highest будет иметь состояние, а состояние будет иметь имя, которое мы хотим передать компоненту Lowest, не обращаясь к компоненту Middle. Вот наш компонент Highest:

class Highest extends Component < state = < name : “React Context API”, >render() < return ( > ) > >

Дочерний компонент будет называться Middle:

const Middle = () => ( 

I’m the middle component

)

В свою очередь, его дочерний компонент будет называться Lowest:

const Lowest = () => ( 
context.name>
)

Как видите, у нас есть состояние Highest, которое мы хотим передать в Lowest. Кроме того, у нас есть статическое свойство, которое позволит нам объявить, каким должен быть наш контекст (здесь это React Context API).

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

Когда лучше не использовать Context API?

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

Также нет необходимости использовать Context API во всем приложении – иначе код может стать слишком беспорядочным. Будьте изобретательны, не используйте Context API, просто чтобы чуть сократить код.

Заключение

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

Полный код мануала вы найдете здесь.

Контекст (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 студентов

Контекст (Context)¶

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

Определение контекста¶

Определение контекста осуществляется при помощи универсальной функции определяющей один обязательный параметр выступающий в качестве инициализационного значения и возвращающей объект контекста createContext(initialValue: T): Context . В случаях когда инициализационное значение в полной мере соответствует предполагаемому типу, чьё описание не включает необязательных членов, то аргументы типа можно или даже нужно не указывать. В остальных случаях это становится необходимостью.

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
import  createContext > from 'react'; /**Аргумента типа не требуется */ interface AContext  a: number; b: string; > /**[0] [1][2] */ export const A = createContext( a: 0, b: ``, >); /** * Поскольку при определении контекста [0] * в качестве обязательного аргумента было * установлено значение [2] полностью соответствующее * предполагаемому типу AContext, аргумент типа * универсальной функции можно опустить [1]. * */ /**Требуется аргумент типа */ interface BContext extends AContext  c?: boolean; > /**[0] [1] */ export const B = createContextBContext>( a: 0, b: ``, >); /** * Так как инициализационное значение [1] * лишь частично соответствует предполагаемому * типу BContext тип объякта контекста необходимо * конкретизировать при помощью аргументов типа [0] */ /**Требуется аргумент типа */ /**[0] [1] */ export const C = createContextBContext | null>(null); /** * По причине отсутствия на момент определения * инициализационного значения оно замененно на null [1], * что требует упомянуть при конкретизации типа значения [0]. */ 

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

Поскольку функциональные и классовые компоненты подразумевают различные подходы для взаимодействия с одними механизмами реализуемыми React, после регистрации контекста в react дереве работа с ним зависит от вида компонента нуждающегося в поставляемых им данных.

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

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
import React,  createContext > from 'react'; /**0 */ const Context = createContext( status: ``, message: ``, >); /** * [0] определение контекста. */ /**[1] */ const App = () => ( /**[2] [3] */ Context.Provider value= <status: `init`, message: `React Context!` >> >/Context.Provider> ); /** * [1] определяем компонент представляющий точку входа * в приложение и регистрируем в его корне Provider [2] * которму при инициализации устанавливаем необходимое значение [3]. */ 

На следующем шаге определим классовый компонент и добавим его в ветку, корнем которой является определенный на предыдущем шаге Provider .

 1 2 3 4 5 6 7 8 9 10 11 12
const App = () => ( Context.Provider value=<status: `init`, message: `React Context!`>>> ClassComponent /> /Context.Provider> ) class ClassComponent extends Component  render() return ( ); > > 

Поскольку компонент является классовым, единственный способ добраться до предоставляемых контекстом данных заключается в создании экземпляра Consumer , который в качестве children ожидает функцию обозначаемую как render callback . Данная функция определяет единственный параметр принадлежащий к типу данных передаваемых с помощью контекста, а возвращаемое ею значение должно принадлежать к любому допустимому типу представляющему элемент React дерева.

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
class ClassComponent extends Component  render()  return ( /**[0] [1] [2] */ Context.Consumer> <(data) => span>data.message>/span>> /Context.Consumer> ); > > /** * Поскольку компонент ClassComponent явялется * классовым, единственный вариант получить в нем * данные предоставляемые контекстом заключается * в создании экземпляра Consumer, который в качестве * children ожидает функцию обозначаемую как render callback * единственный параметр которой принадлежит к типу данных, а * возвращаемое значение должно принадлежать к одному из допустимых * типов предполагаемых React. */ 

Случаи предполагающие определение render callback вне Consumer потребуют указания аннотации типа его единственному параметру, тип для которого лучше объявить в месте определения контекста. Если инициализационные данные в полной мере соответствуют ожидаемому типу, то его получение проще выполнить с помощью механизма запроса типа, чем описывать вручную. В остальных случаях описание потребуется выполнять самостоятельно.

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/**0 */ let initialValue =  status: ``, message: ``, >; const Context = createContext(initialValue); /**[1] [2] */ type ContextType = typeof initialValue; /** * [0] определение инициализационного значения, * на основе которого при помощи запроса типа [2] * будет получен его тип [1]. */ 

Полученный тип необходимо будет указать в аннотации единственного параметра render callback при его определении.

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
class ClassComponent extends Component  /**[0] [1] */ renderCallback = (data: ContextType) => ( span>data.message>/span> ); render() return ( /**[2] */ Context.Consumer >this.renderCallback>/Context.Consumer> ); > > /** * При внешнем [2] определении render callback как поля класса [0] * в аннотации тип его единственного параметра указан тип данных [1] * предоставляемых контекстом. * */ 

Если данные предоставляемые контекстом принадлежать к более общему типу, то параметр render callback можно конкретизировать.

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
/**[0] */ interface Message  message: string; > /**[0] */ interface Status  status: string; > /**[1] [2] */ type ContextType = Message & Status; let initialValue =  status: ``, message: ``, >; const Context = createContext(initialValue); /** * [0] объявление конкретных типов * определяющих тип пересечение [2] * на который ссылается преждний псевдоним [1]. * * Поскольку инициализационное значение в полной * мере соответствует предполагаемому типу, переменную * initialValue и универсальную функцию можно избавить от * явной и излишней конкретизации. */ class ClassComponent extends Component  /**[3] */ renderCallback = (data: Message) => ( span>data.message>/span> ); render()  return ( Context.Consumer> this.renderCallback> /Context.Consumer> ); > > /** * [3] параметр render callback теперь ограничен типом * Message. */ 

Для получения данных распространяемых контекстом внутри тела функционального компонента, помимо варианта с Consumer , который ничем не отличается от рассмотренного в этой теме ранее, предусмотрен более предпочтительный способ предполагающий использование предопределенного хука useContext(context) .

Универсальная функция useContext ожидает в качестве своего единственного аргумента объект контекста, конкретизировать который с помощью аргумента типа не имеет никакого смысла.

 1 2 3 4 5 6 7 8 9 10 11 12 13
const FunctionComponent = () =>  let  message, status > = useContext(Context); return span>message>/span>; >; const App = () => ( Context.Provider value= <status: `init`, message: `React Context!` >> > FunctionComponent /> /Context.Provider> ); 

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

 1 2 3 4 5 6 7 8 9 10 11
const FunctionComponent = () =>  /**[0] [1] */ let  message > = useContextMessage>(Context); // Error return span>message>/span>; >; /** * При попке ограничить тип с помощью аргумента типа [0] * из-за контрвариантности параметров функции возникнет ошибка [1]. */ 

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

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