Введение в контекстный API React
Опубликовано: 2022-03-10Для этого урока у вас должно быть хорошее представление о хуках. Тем не менее, прежде чем мы начнем, я кратко расскажу, что это такое и какие хуки мы будем использовать в этой статье.
Согласно документам React:
“ Хуки — это новое дополнение в React 16.8. Они позволяют вам использовать состояние и другие функции React без написания класса».
Это в основном то, что представляет собой хук React. Это позволяет нам использовать состояние, ссылки и другие функции React в наших функциональных компонентах.
Давайте обсудим два крючка, с которыми мы столкнемся в этой статье.
Хук useState
Хук useState позволяет нам использовать состояние в наших функциональных компонентах. useState
принимает начальное значение нашего состояния в качестве единственного аргумента и возвращает массив из двух элементов. Первый элемент — это наша переменная состояния, а второй элемент — это функция, в которой мы можем использовать обновление значения переменной состояния.
Давайте посмотрим на следующий пример:
import React, {useState} from "react"; function SampleComponent(){ const [count, setCount] = useState(0); }
Здесь count
— это наша переменная состояния, и ее начальное значение равно 0
, а setCount
— это функция, которую мы можем использовать для обновления значения count.
Хук useContext
Я расскажу об этом позже в статье, но этот хук в основном позволяет нам потреблять значение контекста. Что это на самом деле означает, станет более очевидным позже в статье.
Пряжа Рабочие пространства
Рабочие пространства Yarn позволяют организовать кодовую базу проекта с помощью монолитного репозитория (monorepo). React — хороший пример проекта с открытым исходным кодом, который является монорепозиторием и использует рабочие пространства Yarn для достижения этой цели. Читать статью по теме →
Зачем нам нужен Context API?
Мы хотим создать компонент «переключатель тем», который переключается между светлым и темным режимами для нашего приложения React. Каждый компонент должен иметь доступ к текущему режиму темы, чтобы их можно было соответствующим образом стилизовать.
Обычно мы предоставляем текущий режим темы всем компонентам через реквизиты и обновляем текущую тему, используя state
:
import React from "react"; import ReactDOM from "react-dom"; function App() { return ( <div> <Text theme= "blue" /> <h1>{theme}</h1> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
В приведенном выше примере кода мы создали текстовый компонент, который отображает элемент h1
. Цвет элемента h1
зависит от текущего режима темы. В настоящее время тема синяя. Мы можем переключаться между blue
и red
темами с помощью state
.
Мы создадим состояние под названием «тема», используя хук useState
. useState
вернет текущее значение темы и функцию, которую мы можем использовать для обновления темы.
Итак, давайте создадим наше состояние темы:
const [theme, setTheme] = React.useState("blue");
Мы также добавим элемент кнопки в наш компонент App
. Эта кнопка будет использоваться для переключения тем, и для нее требуется обработчик события щелчка. Итак, давайте напишем обработчик события клика так:
const onClickHandler = () => { setTheme(); }
Теперь мы хотим установить Red
тему для новой темы, если текущая тема Blue
, и наоборот. Вместо использования оператора if
более удобный способ сделать это с помощью тернарного оператора в JavaScript.
setTheme( theme === "red"? "blue": "red");
Итак, мы написали наш обработчик onClick
. Давайте добавим этот элемент кнопки в компонент App
:
<button onClick = {onClickHandler}>Change theme</button>
Давайте также изменим значение реквизита темы компонента Text на состояние темы.
<Text theme={theme}/>
Теперь у нас должно быть это:
import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; function App() { const[theme, setTheme] = React.useState("red"); const onClickHandler = () => { setTheme( theme === "red"? "blue": "red"); } return ( <div> <Text theme={theme}/> <button onClick = {onClickHandler}>Change theme</button> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
Теперь мы можем переключаться между нашими двумя темами. Однако, если бы это было приложение гораздо большего размера, было бы сложно использовать тему в глубоко вложенных компонентах, а код стал бы громоздким.
Знакомство с контекстным API
Позвольте мне представить Context API. Согласно документации React:
«Контекст предоставляет способ передачи данных через дерево компонентов без необходимости вручную передавать реквизиты на каждом уровне».
Для более глубокого определения он предоставляет способ сделать определенные данные доступными для всех компонентов в дереве компонентов, независимо от того, насколько глубоко может быть вложен этот компонент.
Давайте посмотрим на этот пример:
const App = () => { return( <ParentComponent theme = "light"/> ); } const ParentComponent = (props) => ( <Child theme = {props.theme} /> ) const Child = (props) => ( <Grandchild theme = {props.theme} /> ) const Grandchild = (props) => ( <p>Theme: {props.theme}</p> )
В приведенном выше примере мы указали тему приложения, используя свойства в ParentComponent
с именем theme
. Нам пришлось передать этот реквизит всем компонентам вниз по дереву компонентов, чтобы получить его там, где он нужен, то есть в компоненте GrandChild
. ChildComponent
не имел ничего общего с реквизитами темы, а просто использовался как посредник.
Теперь представьте, что компонент GrandChild
вложен глубже, чем в верхнем примере. Нам пришлось бы передавать реквизиты темы так же, как мы это сделали здесь, что было бы громоздко. Это проблема, которую решает Context
. С Context
каждый компонент в дереве компонентов имеет доступ к любым данным, которые мы решили поместить в наш контекст.
Начнем с Context
Пришло время воспроизвести кнопку переключения тем, которую мы создали в начале статьи, с помощью Context API. На этот раз наш переключатель тем будет отдельным компонентом. Мы создадим компонент ThemeToggler
, который переключает тему нашего приложения React с помощью Context
.
Во-первых, давайте инициализируем наше приложение React. (Я предпочитаю использовать create-react-app
но вы можете использовать любой другой метод, который вам больше нравится.)
После инициализации проекта React создайте файл ThemeContext.js в папке /src
. Вы также можете создать папку с именем /context
и поместить туда свой файл ThemeContext , если хотите.
Теперь давайте двигаться дальше.
Создание вашего контекстного API
Мы создадим контекст нашей темы в файле ThemeContext.js .
Чтобы создать контекст, мы используем React.createContext
, который создает объект контекста. Вы можете передать что угодно в качестве аргумента React.createContext
. В этом случае мы собираемся передать строку, которая является текущим режимом темы. Итак, теперь наш текущий режим темы — это «светлый» режим темы.
import React from "react"; const ThemeContext = React.createContext("light"); export default ThemeContext;
Чтобы сделать этот контекст доступным для всех наших компонентов React, мы должны использовать Provider. Что такое провайдер? Согласно документации React, каждый объект контекста поставляется с компонентом Provider React , который позволяет потребляющим компонентам подписываться на изменения контекста. Именно поставщик позволяет использовать контекст другими компонентами. Тем не менее, давайте создадим нашего провайдера.
Перейдите к вашему файлу App.js. Чтобы создать нашего провайдера, мы должны импортировать наш ThemeContext
.
После того, как ThemeContext
был импортирован, мы должны заключить содержимое нашего компонента App
в теги ThemeContext.Provider
и предоставить компоненту ThemeContext.Provider
, называемые value
, которые будут содержать данные, которые мы хотим сделать доступными для нашего дерева компонентов.
function App() { const theme = "light"; return ( <ThemeContext.Provider value = {theme}> <div> </div> </ThemeContext.Provider> ); }
Итак, теперь значение «light» доступно для всех наших компонентов (о которых мы скоро напишем).
Создание нашего файла темы
Теперь мы создадим файл нашей темы, который будет содержать разные значения цвета для светлой и темной тем. Создайте в папке /src
файл с именем Colors.js .
В Colors.js мы создадим объект с именем AppTheme
. Этот объект будет содержать цвета для наших тем. Когда вы закончите, экспортируйте объект AppTheme
следующим образом:
const AppTheme = { light: { textColor: "#000", backgroundColor: "#fff" }, dark: { textColor: "#fff", backgroundColor: "#333" } } export default AppTheme;
Теперь пришло время приступить к созданию различных компонентов React.
Создание наших компонентов React
Создадим следующие компоненты:
-
Header
-
ThemeToggler
-
MainWithClass
Заголовок.jsx
import React from "react"; import ThemeToggler from "./ThemeToggler"; const headerStyles = { padding: "1rem", display: "flex", justifyContent: "space-between", alignItems: "center" } const Header = () => { return( <header style = {headerStyles}> <h1>Context API</h1> <ThemeToggler /> </header> ); } export default Header;
ThemeToggler.jsx
(На данный момент мы просто вернем пустой div
.)
import React from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { return( <div style = {themeTogglerStyle}> </div> ); } export default ThemeToggler;
Использование контекста с компонентами на основе классов
Здесь мы будем использовать значение нашего ThemeContext
. Как вы, возможно, уже знаете, у нас есть два метода написания компонентов в React : через функции или классы. Процесс использования контекста в обоих методах отличается, поэтому мы создадим два компонента, которые будут служить основным разделом нашего приложения: MainWithClass
и MainWithFunction
.
Начнем с MainWithClass
.
MainWithClass.jsx
Нам нужно будет импортировать наши ThemeContext
и AppTheme
. Как только это будет сделано, мы напишем класс, который возвращает наш JSX из метода рендеринга. Теперь нам нужно использовать наш контекст. Есть два способа сделать это с компонентами на основе классов:
- Первый метод — через
Class.contextType
.
Чтобы использовать этот метод, мы назначаем объект контекста из нашегоThemeContext
свойствуcontextType
нашего класса. После этого мы сможем получить доступ к значению контекста, используяthis.context
. Вы также можете ссылаться на это в любом из методов жизненного цикла и даже в методе рендеринга.import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context]; return( <main></main> ); } }
После присвоенияThemeContext
свойствуcontextType
нашего класса я сохранил текущий объект темы в переменнойcurrentTheme
.
Теперь мы возьмем цвета из переменнойcurrentTheme
и используем их для оформления разметки.render() { const currentTheme = AppTheme[this.context]; return ( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main>
Вот и все! Однако этот метод ограничивает использование только одного контекста. - Второй метод
ThemeContext.Consumer
предполагает использование Consumer. Каждый объект контекста также поставляется с компонентом Consumer React, который можно использовать в компоненте на основе классов. Компонент-потребитель принимает дочерний элемент как функцию, и эта функция возвращает узел React. Текущее значение контекста передается этой функции в качестве аргумента.
Теперь давайте заменим код в нашем компонентеMainWithClass
следующим:class Main extends Component { constructor() { super(); this.state = { } } render(){ return( <ThemeContext.Consumer> { (theme) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } }
Как видите, мы использовали текущее значение нашегоThemeContext
, которое мы назвали «theme», и мы взяли значения цвета для этого режима темы и присвоили их переменнойcurrentTheme
. С помощью этого метода вы можете использовать несколько потребителей.
Это два метода использования контекста с компонентами на основе классов.
Использование контекста с функциональными компонентами
Использование контекста с функциональными компонентами проще и менее утомительно, чем с компонентами на основе классов. Чтобы использовать контекст в функциональном компоненте, мы будем использовать хук с именем useContext
.
Вот как будет выглядеть использование нашего ThemeContext
с функциональным компонентом:
const Main = () => { const theme = useContext(ThemeContext); const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;
Как видите, все, что нам нужно было сделать, это использовать наш хук useContext
с нашим ThemeContext
переданным в качестве аргумента.
Примечание . Вы должны использовать эти различные компоненты в файле App.js, чтобы увидеть результаты.
Обновление нашей темы с помощью компонента ThemeToggler
Теперь мы собираемся работать над нашим компонентом ThemeToggler
. Нам нужно иметь возможность переключаться между светлой и темной темами. Для этого нам нужно отредактировать наш ThemeContext.js . Наш React.createContext
теперь будет принимать в качестве аргумента объект, напоминающий результат хука useState
.
const ThemeContext = React.createContext(["light", () => {}]);
Мы передали массив функции React.createContext
. Первый элемент массива — это текущий режим темы, а второй элемент — это функция, которая будет использоваться для обновления темы. Как я уже сказал, это просто похоже на результат хука useState
, но это не совсем результат хука useState
.
Теперь мы отредактируем наш файл App.js. Нам нужно изменить значение, переданное провайдеру, на хук useState
. Теперь значением нашего Theme Context является хук useState
, значение по умолчанию которого — «light».
function App() { const themeHook = useState("light"); return ( <ThemeContext.Provider value = {themeHook}> <div> <Header /> <Main /> </div> </ThemeContext.Provider> ); }
Пишем наш компонент ThemeToggler
Давайте теперь напишем наш компонент ThemeToggler
:
import React,{useContext} from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { const[themeMode, setThemeMode] = useContext(ThemeContext); return( <div style = {themeTogglerStyle} onClick = {() => {setThemeMode(themeMode === "light"? "dark": "light")}}> <span title = "switch theme"> {themeMode === "light" ? "" : "️"} </span> </div> ); } export default ThemeToggler;
Поскольку значение контекста нашей темы теперь является ловушкой всякий раз, когда мы вызываем для него useContext
, оно возвращает массив. Используя деструктурирование, мы смогли получить элементы из массива. Затем мы написали обработчик события onClick
для нашего ThemeToggler
. С этим кодом всякий раз, когда нажимается переключатель темы, он переключает тему нашего приложения.
Теперь мы будем редактировать разные версии нашего Main
компонента.
Редактирование нашего компонента MainWithClass
- Версия компонента
MainWithClass
, использующая методClass.contextType
:import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context[0]]; return( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } }
- Версия компонента
MainWithClass
, использующая методThemeContext.Consumer
:import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component { constructor() { super(); this.state = {} } render() { return ( <ThemeContext.Consumer> { ([theme]) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } } export default Main;
Редактирование нашего компонента MainWithFunction
Компонент MainWithFunction
следует отредактировать следующим образом:
import React, { useContext } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; const Main = () => { const theme = useContext(ThemeContext)[0]; const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;
Заключение
Вот и все! Нам удалось реализовать два режима темы для нашего приложения React с помощью Context API.
В процессе мы узнали:
- Что такое Context API и какие проблемы он решает;
- Когда использовать Context API;
- Создание
Context
и его использование как в функциональных компонентах, так и в компонентах на основе классов.
Дальнейшее чтение на SmashingMag:
- Стилизация в современных веб-приложениях
- Создание мобильных приложений с помощью Ionic и React
- Создайте PWA с помощью Webpack и Workbox
- Знакомство с MutationObserver API