Руководство по React Context и хуку useContext()
1. Как использовать контекст
Содержание статьи:
Для использования контекста в React требуется 3 простых шага: создание контекста, предоставление контекста и использование контекста.
А. Создание контекста
Встроенная функция createContext(default) создает экземпляр контекста:
JavaScript import { createContext } from ‘react’; const Context = createContext(‘Default Value’);
12 | import { createContext } from ‘react’;const Context = createContext(‘Default Value’); |
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
Функция принимает один необязательный аргумент: значение по умолчанию.
Б. Предоставление контекста
Компонент сontext.Provider, доступный в экземпляре контекста, используется для предоставления контекста его дочерним компонентам, независимо от того, насколько они глубоки. Чтобы установить значение контекста, используйте свойство value, доступное через <Context.Provider value={value} />:
JavaScript function Main() { const value = ‘My Context Value’; return ( <Context.Provider value={value}> <MyComponent /> </Context.Provider> ); }
12345678 | function Main() { const value = ‘My Context Value’; return ( <Context.Provider value={value}> <MyComponent /> </Context.Provider> );} |
Если вы хотите изменить значение контекста, просто обновите свойство value.
C. Использование контекста
Использование контекста может быть выполнено двумя способами. Первый способ, который я рекомендую, — это использовать React хук useContext(Context):
JavaScript import { useContext } from ‘react’; function MyComponent() { const value = useContext(Context); return <span>{value}</span>; }
12345 | import { useContext } from ‘react’;function MyComponent() { const value = useContext(Context); return <span>{value}</span>;} |
Попробуйте демо.
Хук возвращает значение контекста: value = useContext(Context). Также он гарантирует повторный рендеринг компонента при изменении значения контекста.
Второй способ — использовать функцию рендеринга, предоставленную в качестве дочернего компонента Context.Consumer, доступного в экземпляре контекста:
JavaScript function MyComponent() { return ( <Context.Consumer> {value => <span>{value}</span>} </Context.Consumer> ); }
1234567 | function MyComponent() { return ( <Context.Consumer> {value => <span>{value}</span>} </Context.Consumer> );} |
Попробуйте демо.
Опять же, в случае, если значение контекста изменится, функция рендеринга <Context.Consumer> будет повторно визуализирована.
Вы можете иметь столько потребителей, сколько хотите для одного контекста. Если значение контекста изменяется (путем изменения свойства value в <Context.Provider value={value} />), все потребители немедленно уведомляются и повторно обрабатываются.
Если потребитель не заключен внутри провайдера, но все же пытается получить доступ к значению контекста (используя useContext(Context) или <Context.Consumer>), тогда значение контекста будет аргументом значения по умолчанию, предоставленным функции createContext(defaultValue), которая создала контекст.
2. Когда вам нужен контекст?
Основная идея использования контекста — предоставить вашим компонентам доступ к некоторым глобальным данным и повторный рендеринг при изменении этих данных. Контекст решает проблему когда вам нужно передать свойства от родителей потомкам.
Вы можете держать внутри контекста:
глобальное состояние
тему
конфигурацию приложения
аутентифицированное имя пользователя
пользовательские настройки
предпочтительный язык
набор услуг
С другой стороны, вы должны хорошо подумать, прежде чем принимать решение об использовании контекста в своем приложении.
Во-первых, интеграция контекста добавляет сложности. Создание контекста, обертывание его в провайдере, использование useContext() в каждом потребителе — это увеличивает сложность.
Во-вторых, добавление контекста затрудняет модульное тестирование компонентов. Во время модульного тестирования вам придется заключить потребительские компоненты в поставщик контекста, включая компоненты, на которые косвенно влияет контекст — предков потребителей контекста!
3. Пример использования: глобальное имя пользователя.
Самый простой способ передать данные от родителя к дочернему компоненту — это когда родитель присваивает свойства своему дочернему компоненту:
JavaScript function Application() { const userName = “John Smith”; return <UserInfo userName={userName} />; } function UserInfo({ userName }) { return <span>{userName}</span>; }
1234567 | function Application() { const userName = “John Smith”; return <UserInfo userName={userName} />;}function UserInfo({ userName }) { return <span>{userName}</span>;} |
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Получить курс сейчас!
Родительский компонент <Application /> присваивает данные userName своему дочернему компоненту <UserInfo name={userName} /> с помощью свойства userName.
Это обычный способ передачи данных с использованием свойств. Вы можете использовать этот подход без проблем.
Ситуация меняется, когда дочерний компонент <UserInfo /> не является прямым потомком <Application />, но содержится в нескольких предках.
Например, предположим, что компонент <Application /> (тот, у которого есть глобальные данные userName) визуализирует компонент <Layout />, который, в свою очередь, визуализирует компонент <Header />, который, в свою очередь, наконец, визуализирует компонент <UserInfo /> (который хотел бы получить доступ к userName). Вот как будет выглядеть такое структурирование:
JavaScript function Application() { const userName = “John Smith”; return ( <Layout userName={userName}> Main content </Layout> ); } function Layout({ children, userName }) { return ( <div> <Header userName={userName} /> <main> {children} </main> </div> ) } function Header({ userName }) { return ( <header> <UserInfo userName={userName} /> </header> ); } function UserInfo({ userName }) { return <span>{userName}</span>; }
12345678910111213141516171819202122232425262728 | function Application() { const userName = “John Smith”; return ( <Layout userName={userName}> Main content </Layout> );}function Layout({ children, userName }) { return ( <div> <Header userName={userName} /> <main> {children} </main> </div> )}function Header({ userName }) { return ( <header> <UserInfo userName={userName} /> </header> );}function UserInfo({ userName }) { return <span>{userName}</span>;} |
Попробуйте демо.
Вы можете заметить проблему: компонент <UserInfo /> отображается глубоко в дереве, и все родительские компоненты (<Layout /> и <Header />) должны передавать свойство userName. Эта проблема также известна как дриллинг свойств.
Контекст React — возможное решение. Давайте посмотрим, как его применить в следующем разделе.
3.1 Контекст в помощь
Напоминаем, что для применения контекста React требуются 3 участника: контекст, поставщик, извлеченный из контекста, и потребитель. Вот как будет выглядеть пример приложения при применении к нему контекста:
JavaScript import { useContext, createContext } from ‘react’; const UserContext = createContext(‘Unknown’); function Application() { const userName = “John Smith”; return ( <UserContext.Provider value={userName}> <Layout> Main content </Layout> </UserContext.Provider> ); } function Layout({ children }) { return ( <div> <Header /> <main> {children} </main> </div> ); } function Header() { return ( <header> <UserInfo /> </header> ); } function UserInfo() { const userName = useContext(UserContext); return <span>{userName}</span>; }
123456789101112131415161718192021222324252627282930313233 | import { useContext, createContext } from ‘react’;const UserContext = createContext(‘Unknown’);function Application() { const userName = “John Smith”; return ( <UserContext.Provider value={userName}> <Layout> Main content </Layout> </UserContext.Provider> );}function Layout({ children }) { return ( <div> <Header /> <main> {children} </main> </div> );}function Header() { return ( <header> <UserInfo /> </header> );}function UserInfo() { const userName = useContext(UserContext); return <span>{userName}</span>;} |
Попробуйте демо.
Разберемся подробнее, что было сделано. Сначала создается контекст const UserContext = createContext(‘Unknown’), в котором будет храниться информация об имени пользователя.
Потом, внутри компонента <Application />, дочерние компоненты помещаются внутри провайдера контекста пользователя: <UserContext.Provider value={userName}>. Обратите внимание на свойство value компонента поставщика: именно так вы устанавливаете значение контекста.
Наконец, <UserInfo /> становится потребителем контекста с помощью встроенного хука useContext(UserContext). Хук вызывается с контекстом в качестве аргумента и возвращает значение имени пользователя.
Промежуточные компоненты <Layout /> и <Header /> не должны передаваться свойством userName. В этом большое преимущество контекста: он снимает бремя передачи данных через промежуточные компоненты.
3.2 При изменении контекста
Когда значение контекста изменяется путем изменения свойства value поставщика контекста (<Context.Provider value={value} />), все его потребители уведомляются и повторно обрабатываются.
Например, если я изменю имя пользователя с ‘John Smith’ на ‘Smith, John Smith’, то потребитель <UserInfo /> немедленно выполнит повторный рендеринг, чтобы отобразить последнее значение контекста:
JavaScript import { createContext, useEffect, useState } from ‘react’; const UserContext = createContext(‘Unknown’); function Application() { const [userName, setUserName] = useState(‘John Smith’); useEffect(() => { setTimeout(() => { setUserName(‘Smith, John Smith’); }, 2000); }, []); return ( <UserContext.Provider value={userName}> <Layout> Main content </Layout> </UserContext.Provider> ); } // …
123456789101112131415161718 | import { createContext, useEffect, useState } from ‘react’;const UserContext = createContext(‘Unknown’);function Application() { const [userName, setUserName] = useState(‘John Smith’); useEffect(() => { setTimeout(() => { setUserName(‘Smith, John Smith’); }, 2000); }, []); return ( <UserContext.Provider value={userName}> <Layout> Main content </Layout> </UserContext.Provider> );}// … |
Откройте демонстрацию, и вы увидите ‘John Smith’ (значение контекста), отображаемое на экране. Через 2 секунды значение контекста изменится на ‘Smith, John Smith’, и, соответственно, новое значение будет показано на экране.
Демонстрация показывает, что компонент потребитель <UserInfo />, отображает значение контекста на экране, при изменении значения контекста повторно отображает его значение.
JavaScript function UserInfo() { const userName = useContext(UserContext); return <span>{userName}</span>; }
1234 | function UserInfo() { const userName = useContext(UserContext); return <span>{userName}</span>;} |
4. Обновление контекста
React Context API по умолчанию не имеет состояния и не предоставляет специальный метод для обновления значения контекста из компонентов-потребителей.
Но это может быть легко реализовано путем интеграции механизма управления состоянием (подобно хукам useState() и useReducer()), и обеспечения функции обновления прямо в контексте рядом с самим значением. В следующем примере компонент <Application /> использует хук useState() для управления значением контекста.
JavaScript import { createContext, useState, useContext, useMemo } from ‘react’; const UserContext = createContext({ userName: ”, setUserName: () => {}, }); function Application() { const [userName, setUserName] = useState(‘John Smith’); const value = useMemo( () => ({ userName, setUserName }), [userName] ); return ( <UserContext.Provider value={value}> <UserNameInput /> </UserContext.Provider> ); } function UserNameInput() { const { userName, setUserName } = useContext(UserContext); const changeHandler = event => setUserName(event.target.value); return ( <input type=”text” value={userName} onChange={changeHandler} /> ); } function UserInfo() { const { userName } = useContext(UserContext); return <span>{userName}</span>; }
123456789101112131415161718192021222324252627282930313233 | import { createContext, useState, useContext, useMemo } from ‘react’;const UserContext = createContext({ userName: ”, setUserName: () => {},});function Application() { const [userName, setUserName] = useState(‘John Smith’); const value = useMemo( () => ({ userName, setUserName }), [userName] ); return ( <UserContext.Provider value={value}> <UserNameInput /> </UserContext.Provider> );}function UserNameInput() { const { userName, setUserName } = useContext(UserContext); const changeHandler = event => setUserName(event.target.value); return ( <input type=”text” value={userName} onChange={changeHandler} /> );}function UserInfo() { const { userName } = useContext(UserContext); return <span>{userName}</span>;} |
Попробуйте демо.
Потребитель <UserNameInput /> читает значение контекста, откуда извлекаются userName и setUserName. Затем потребитель может обновить значение контекста, вызвав функцию обновления setUserName(newContextValue).
<UserInfo /> — еще один потребитель контекста. Когда обновляет контекст — обновляется и компонент <UserInfo />.
Обратите внимание, что <Application /> запоминает значение контекста. Объект значения контекста сохраняется неизменным до тех пор, пока userName остается неизменным, предотвращая повторную визуализацию потребителей каждый раз при повторной визуализации <Application />.
В противном случае во время повторного рендеринга будут созданы разные экземпляры объекта, что вызовет повторный рендеринг <Application /> в потребителях контекста.
5. Вывод
Контекст в React — это концепция, которая позволяет вам снабжать дочерние компоненты глобальными данными, независимо от того, насколько глубоко они находятся в дереве компонентов.
Для использования контекста требуется 3 шага: создание, предоставление и использование контекста. При интеграции контекста в ваше приложение учтите, что иногда проще передать свойство через 2–3 уровня иерархии чем использовать контекст, потому что он значительно усложняет работу.
Автор: Dmitri Pavlutin
Источник: webformyself.com