Главная » Статьи » Руководство по React Context и хуку useContext()

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

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

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

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

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

А. Создание контекста

Встроенная функция createContext(default) создает экземпляр контекста:

import { createContext } from 'react';
const Context = createContext('Default Value');

Функция принимает один необязательный аргумент: значение по умолчанию.

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

Компонент сontext.Provider, доступный в экземпляре контекста, используется для предоставления контекста его дочерним компонентам, независимо от того, насколько они глубоки. Чтобы установить значение контекста, используйте свойство value, доступное через <Context.Provider value={value} />:

function Main() { const value = 'My Context Value'; return ( <Context.Provider value={value}> <MyComponent /> </Context.Provider> );
}

Если вы хотите изменить значение контекста, просто обновите свойство value.

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

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

import { useContext } from 'react';
function MyComponent() { const value = useContext(Context); return <span>{value}</span>;
}

Попробуйте демо.

Хук возвращает значение контекста: value = useContext(Context). Также он гарантирует повторный рендеринг компонента при изменении значения контекста.

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

function MyComponent() { return ( <Context.Consumer> {value => <span>{value}</span>} </Context.Consumer> );
}

Попробуйте демо.

Опять же, в случае, если значение контекста изменится, функция рендеринга <Context.Consumer> будет повторно визуализирована.

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

Вы можете иметь столько потребителей, сколько хотите для одного контекста. Если значение контекста изменяется (путем изменения свойства value в <Context.Provider value={value} />), все потребители немедленно уведомляются и повторно обрабатываются.

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

2. Когда вам нужен контекст?

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

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

глобальное состояние

тему

конфигурацию приложения

аутентифицированное имя пользователя

пользовательские настройки

предпочтительный язык

набор услуг

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

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

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

3. Пример использования: глобальное имя пользователя.

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

function Application() { const userName = "John Smith"; return <UserInfo userName={userName} />;
}
function UserInfo({ userName }) { return <span>{userName}</span>;
}

Родительский компонент <Application /> присваивает данные userName своему дочернему компоненту <UserInfo name={userName} /> с помощью свойства userName.

Это обычный способ передачи данных с использованием свойств. Вы можете использовать этот подход без проблем.
Ситуация меняется, когда дочерний компонент <UserInfo /> не является прямым потомком <Application />, но содержится в нескольких предках.

Например, предположим, что компонент <Application /> (тот, у которого есть глобальные данные userName) визуализирует компонент <Layout />, который, в свою очередь, визуализирует компонент <Header />, который, в свою очередь, наконец, визуализирует компонент <UserInfo /> (который хотел бы получить доступ к userName). Вот как будет выглядеть такое структурирование:

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 участника: контекст, поставщик, извлеченный из контекста, и потребитель. Вот как будет выглядеть пример приложения при применении к нему контекста:

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 /> немедленно выполнит повторный рендеринг, чтобы отобразить последнее значение контекста:

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 />, отображает значение контекста на экране, при изменении значения контекста повторно отображает его значение.

function UserInfo() { const userName = useContext(UserContext); return <span>{userName}</span>;
}

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

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

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

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

Источник: dmitripavlutin.com

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен