Как работать с контекстом в React?
2 года назад·5 мин. на чтение
Контекст позволяет родительскому компоненту сделать некоторую информацию доступной для любого компонента в дереве под ним — независимо от того, насколько он глубок — без явной передачи ее через пропсы.
Обычно вы передаете информацию от родительского компонента дочернему компоненту через пропс. Но передача пропса может стать многословной и неудобной, если вам придется передавать их через множество компонентов посередине или если многим компонентам вашего приложения требуется одна и та же информация.
Допустим, вы хотите, чтобы несколько заголовков в одном разделе всегда имели одинаковый размер:
Проблема с передачей пропсов в React
Передача пропсов - отличный способ явно передать данные через UI дерево компонентам, которые его используют. Но передача пропса может стать многословным и неудобным, когда вам нужно передать какой-то проп глубоко через дерево, или если многим компонентам нужен один и тот же проп. Ближайший общий предок может быть далек от компонентов, которые нуждаются в данных, и поднятие состояния вверх может привести к ситуации, иногда называемой “prop drilling”. Было бы здорово, просто «телепортировать» данные к компонентам дерева, которые в них нуждаются.Контекст: альтернатива передаче пропсов
Контекст позволяет родительскому компоненту предоставлять данные всему дереву под ним. Есть много применений для контекста. Вот один из примеров. Рассмотрим компонентHeading
, который принимает level
для своего размера:
// App.js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return ( <Section> <Heading level={1}>Title</Heading> <Heading level={2}>Heading</Heading> <Heading level={3}>Sub-heading</Heading> <Heading level={4}>Sub-sub-heading</Heading> <Heading level={5}>Sub-sub-sub-heading</Heading> <Heading level={6}>Sub-sub-sub-sub-heading</Heading> </Section> ); }
// Section.js export default function Section({ children }) { return ( <section className="section"> {children} </section> ); }
// Heading.js export default function Heading({ level, children }) { switch (level) { case 1: return <h1>{children}</h1>; case 2: return <h2>{children}</h2>; case 3: return <h3>{children}</h3>; case 4: return <h4>{children}</h4>; case 5: return <h5>{children}</h5>; case 6: return <h6>{children}</h6>; default: throw Error('Unknown level: ' + level); } }
В настоящее время вы передаете проп// App.js import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return ( <Section> <Heading level={1}>Title</Heading> <Section> <Heading level={2}>Heading</Heading> <Heading level={2}>Heading</Heading> <Heading level={2}>Heading</Heading> <Section> <Heading level={3}>Sub-heading</Heading> <Heading level={3}>Sub-heading</Heading> <Heading level={3}>Sub-heading</Heading> <Section> <Heading level={4}>Sub-sub-heading</Heading> <Heading level={4}>Sub-sub-heading</Heading> <Heading level={4}>Sub-sub-heading</Heading> </Section> </Section> </Section> </Section> ); }
level
каждому <Heading>
отдельно:
Было бы неплохо, если бы вы могли передать проп<Section> <Heading level={3}>About</Heading> <Heading level={3}>Photos</Heading> <Heading level={3}>Videos</Heading> </Section>
level
в <Section>
и удалить его из <Heading>
. Таким образом, вы можете добиться того, чтобы все заголовки в одном разделе имели одинаковый размер:
Но как<Section level={3}> <Heading>About</Heading> <Heading>Photos</Heading> <Heading>Videos</Heading> </Section>
<Heading>
может узнать уровень своего ближайшего <Section>
? Для этого дочернему компоненту потребуется какой-то способ «попросить» данные откуда-то сверху на дереве.
Вы не можете сделать это только с пропсами. Именно здесь в игру вступает контекст. Вы можете сделать это в три шага:
- Создайте контекст. (Вы можете назвать его
LevelContext
, так как он предназначен для уровня заголовка) - Используйте этот контекст из компонента, которому требуются данные. (
Heading
будет использоватьLevelContext
) - Укажите этот контекст из компонента, задающего данные. (
Section
будет содержатьLevelContext
.)
Шаг 1: Создайте контекст
Во-первых, вам нужно создать контекст. Вам нужно будет экспортировать его из файла, чтобы ваши компоненты могли использовать его:Единственным аргументом// LevelContext.js import { createContext } from 'react'; export const LevelContext = createContext(1);
createContext
является значение по умолчанию. Здесь 1
относится к самому большому уровню заголовка, но вы можете передать любое значение (даже объект). Вы увидите значение значения по умолчанию на следующем шаге.
Шаг 2: Используйте контекст
Импортируйте хукuseContext
из React и импортируйте контекст, созданный на первом шаге:
Как вы помните компонент// Heading.js import { useContext } from 'react'; import { LevelContext } from './LevelContext.js';
Heading
считывает level
из пропса:
Вместо этого удалите пропexport default function Heading({ level, children }) { // ... }
level
и прочитайте значение из только что импортированного контекста LevelContext
:
export default function Heading({ children }) { const level = useContext(LevelContext); // ... }
useContext
— это хук. Так же, как useState
и useReducer
, вы можете вызвать хук только на верхнем уровне компонента React. useContext
сообщает React, что компонент Heading
хочет прочитать LevelContext
.
Теперь, когда компонент Heading
не имеет пропса level
, вам больше не нужно передавать проп level
в Heading
в вашем JSX.
Обновите JSX так, чтобы его получал Section
:
Обратите внимание, что этот пример еще не совсем работает. Все заголовки имеют одинаковый размер, потому что хоть мы и вызвали контекст, но еще не предоставили его. React не знает, где его взять. Если контекст не указан, React будет использовать значение по умолчанию, указанное на предыдущем шаге. В этом примере в качестве аргумента<Section level={4}> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> <Heading>Sub-sub-heading</Heading> </Section>
createContext
мы передали 1
, поэтому useContext(LevelContext)
возвращает 1
, установив для всех этих заголовков <h1>
. Давайте исправим эту проблему, предоставив каждому Section
свой собственный контекст.
Шаг 3: Предоставьте контекст
КомпонентSection
в настоящее время рендерит дочерние компоненты. Оберните их поставщиком контекста (context provider), чтобы предоставить им LevelContext
:
Это говорит React: «Если какой-либо компонент внутри этогоimport { LevelContext } from './LevelContext.js'; export default function Section({ level, children }) { return ( <section className="section"> <LevelContext.Provider value={level}> {children} </LevelContext.Provider> </section> ); }
<Section>
просит LevelContext
, дайте им этот level
». Компонент будет использовать значение ближайшего <LevelContext.Provider>
в UI дереве над ним.
Этот код будет работать так же как и в самой первой реализации. Но вам не нужно было передавать проп level
каждому компоненту Heading
. Вместо этого он «выясняет» свой уровень заголовка, спрашивая ближайший Section
выше:
- Вы передаете проп
level
в<Section>
. Section
оборачивает свои дочерние элементы в<LevelContext.Provider value={level}>
.Heading
запрашивает ближайшее значениеLevelContext
выше сuseContext(LevelContext)
.
Типичные варианты использования контекста
- Темы: Если ваше приложение позволяет пользователю изменять его внешний вид (например, в темном режиме), вы можете поместить поставщика контекста в верхнюю часть приложения и использовать этот контекст в компонентах, которые должны настроить свой визуальный вид.
- Текущий пользователь приложения: Многим компонентам может потребоваться знать текущего вошедшего в систему пользователя. Помещение его в контекст позволяет удобно читать его в любом месте дерева. Некоторые приложения также позволяют управлять несколькими учетными записями одновременно (например, оставлять комментарий как другой пользователь). В этих случаях может быть удобно обернуть часть пользовательского интерфейса во вложенного поставщика с другим значением текущего счета.
- Маршрутизация: Большинство решений маршрутизации используют внутренний контекст для хранения текущего маршрута. Таким образом, каждая ссылка «знает», активна она или нет. Если вы создаете свой собственный маршрутизатор, вы тоже можете это сделать.
- Управление состоянием: По мере роста вашего приложения вы можете получить большое состояние ближе к верхней части вашего приложения. Многие отдаленные компоненты ниже могут захотеть изменить его. Обычно используется редьюсер вместе с контекстом для управления сложным состоянием и передачи его удаленным компонентам без особых хлопот.
Итоги
- Контекст позволяет компоненту предоставлять некоторую информацию всему дереву под ним.
-
Чтобы передать контекст:
- Создайте и экспортируйте его с
export const MyContext = createContext(defaultValue)
. - Передайте его в хук
useContext(MyContext)
чтобы прочитать его в любом дочернем компоненте, независимо от глубины. - Оберните дочерние комоненты в
<MyContext.Provider value={...}>
для представления контекста из родительского компонента.
- Создайте и экспортируйте его с
- Контекст проходит через любые компоненты посередине.
- Контекст позволяет писать компоненты, которые «адаптируются к окружающей среде».
-
Прежде чем использовать контекст, попробуйте передать проп или передать JSX как
children
.
Как переключать css класс в React
год назад·2 мин. на чтение
В этой статье рассмотрим, как динамически переключать css классы в приложении React.
Переключение ccs классов
Чтобы переключить css класс, нам нужно использовать логическое значение, например, в тернарном выражении.- Если логическое значение
true
, имя класса добавляется к элементу react. - Если логическое значение
false
имя класса удаляется из элемента react.
app
в элемент div
, когда мы нажимаем кнопку Toggle class
. Если мы нажмем эту кнопку еще раз, имя класса будет удалено из элемента div
.
// App.js import React, { useState } from "react"; import "./styles.css"; export default function App() { const [isActive, setActive] = useState(false); const handleToggle = () => { setActive(!isActive); }; return ( <div className={isActive ? "app" : null}> <h1>Hello react</h1> <button onClick={handleToggle}>Toggle class</button> </div> ); }
// App.js import React, { Component } from "react"; import "./styles.css"; class App extends Component { state = { isActive: false }; handleToggle = () => { this.setState({ isActive: !this.state.isActive }); }; render() { const isActive = this.state.isActive; return ( <div className={isActive ? "app" : null}> <h1>Hello react</h1> <button onClick={this.handleToggle}>Toggle class</button> </div> ); } } export default App;
Переключение между двумя именами классов
Вместо того, чтобы добавлять или удалять имя класса, мы также можем переключаться между двумя именами классов в приложении React. В приведенном ниже примере по умолчанию классcontainer
добавляется в элемент div
, когда мы нажимаем кнопку Toggle class
класс container
удаляется, а класс app
добавляется к элементу.
// App.js import React, { useState } from "react"; import "./styles.css"; export default function App() { const [isActive, setActive] = useState(false); const handleToggle = () => { setActive(!isActive); }; return ( <div className={isActive ? "app" : "container"}> <h1>Hello react</h1> <button onClick={handleToggle}>Toggle class</button> </div> ); }
Добавление имени класса к существующему имени класса
Вы также можете добавить новое имя класса к существующему имени класса следующим образом.Первоначальный вид html разметки:// App.js import React, { useState } from "react"; import "./styles.css"; export default function App() { const [isActive, setActive] = useState(false); const handleToggle = () => { setActive(!isActive); }; return ( <div className={`app ${isActive ? "danger" : ""}`}> <h1>Hello react</h1> <button onClick={handleToggle}>Toggle class</button> </div> ); }
После нажатия кнопки<div class="app"> <h1>Hello react</h1> <button>Toggle class</button> </div>
Toggle class
html выглядит следующим образом.
<div class="app danger"> <h1>Hello react</h1> <button>Toggle class</button> </div>