В React контекст — это способ передачи данных между деревьями компонентов без ручного добавления реквизитов для каждого слоя компонентов. Контекст предоставляет способ совместного использования указанных значений между компонентами без необходимости явно передавать их через компонент. Передавать реквизиты слой за слоем. в дереве.
Операционная среда этого руководства: система Windows 10, версия 17.0.1, компьютер Dell G3.
Контекст предоставляет способ передачи данных между деревьями компонентов без ручного добавления реквизитов к каждому слою компонентов. В типичном приложении React данные передаются сверху вниз (от родителя к дочернему элементу) через реквизиты, но этот подход чрезвычайно громоздок для определенных типов свойств (например, настройки локали, темы пользовательского интерфейса), эти свойства требуются многим компонентам в приложение. Контекст предоставляет способ совместного использования таких значений между компонентами без необходимости явно передавать реквизиты через каждый уровень дерева компонентов.
Контекст предназначен для обмена данными, которые являются «глобальными» для дерева компонентов, например данные о текущем аутентифицированном пользователе, теме или предпочитаемом языке. Например, в следующем коде мы вручную настраиваем стиль компонента кнопки с помощью атрибута «theme».
class App расширяет React.Component { render() { return <Toolbar theme="dark" /> }}function Toolbar(props) { // Компонент Toolbar принимает дополнительное свойство "theme", которое затем передается в ThemedButton; компонент. // Было бы проблематично, если бы каждая кнопка в приложении должна была знать значение темы, // потому что это значение пришлось бы передавать всем компонентам. return ( <p> <ThemedButton theme={props.theme} /> </p> );}class ThemedButton расширяет React.Component { render() { return <Button theme={this.props.theme} /> }; }// Пропускаем реквизиты: App -> Toolbar -> ThemedButton // Если вложенность очень глубокая, то реквизиты нужно передавать слой за слоем, даже если в середине реквизиты не нужны, это кажется очень громоздким.Используя контекст, мы можем избежать передачи реквизита через промежуточные элементы.
// Контекст позволяет нам передавать значения глубоко в дерево компонентов без необходимости явно передавать их через каждый компонент. // Создаем контекст для текущей темы («светлый» — значение по умолчанию). const ThemeContext = React.createContext('light');class App расширяет React.Component { render() { // Используйте поставщика для передачи текущей темы в следующее дерево компонентов. // Независимо от глубины, любой компонент может прочитать это значение. // В этом примере мы передаем «темный» в качестве текущего значения. return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> }}// Промежуточному компоненту больше не нужно указывать тему, которую нужно передать. function Toolbar() { return ( <p> <ThemedButton /> </p> );}class ThemedButton расширяет React.Component { // Укажите contextType для чтения текущего контекста темы. // React найдет ближайшего поставщика тем и использует его значение. // В этом примере текущее значение темы — «темная». static contextType = ThemeContext; render() { return <Button theme={this.context} /> }// Вы также можете использовать ThemedButto.contextType = ThemeContext;Создайте объект контекста. Когда React отображает компонент, подписанный на этот объект Context, компонент считывает текущее значение контекста из соответствующего провайдера, ближайшего к нему в дереве компонентов.
Только если в дереве, где расположен компонент, нет соответствующего поставщика, вступит в силу его параметр defaultValue. Это помогает тестировать компоненты без использования поставщика для упаковки компонента. Примечание. Если в значение поставщика передается неопределенное значение, значение defaultValue потребляющего компонента не вступит в силу.
Каждый объект Context возвращает компонент Provider React, который позволяет компонентам-потребителям подписываться на изменения контекста.
Поставщик получает атрибут значения и передает его потребляющему компоненту. Поставщик может иметь соответствующие отношения с несколькими потребительскими компонентами. Несколько поставщиков также могут использоваться вложенными, и внутренний уровень перезапишет данные внешнего уровня.
Когда значение Provider изменится, все потребляющие компоненты внутри него будут перерисованы. Ни поставщик, ни его внутренние потребительские компоненты не подлежат функции mustComponentUpdate, поэтому потребительский компонент может обновляться, даже если его родительский компонент прекращает обновление.
Атрибут contextType, установленный в классе, будет переназначен объекту Context, созданному React.createContext(). Это позволяет вам использовать this.context для использования значения в самом последнем контексте. Вы можете получить к нему доступ в любом жизненном цикле, включая функцию рендеринга.
import MyContext from './MyContext';class MyClass расширяет React.Component { компонентDidMount() { let value = this.context /* После того, как компонент смонтирован, используйте значение компонента MyContext для выполнения некоторых операций с побочным эффектом* / } компонентDidUpdate() { let value = this.context; /* ... */ } компонентWillUnmount() { let value = this.context; /* ... */ } render() { let value = this.context ; /* Отображение на основе значения компонента MyContext*/ } // Или используйте static contextType = MyContext;}MyClass.contextType = MyContext;Здесь компоненты React также могут подписываться на изменения контекста. Это позволяет подписаться на контекст в функциональных компонентах.
Это требует функции ребенка. Эта функция получает текущее значение контекста и возвращает узел React. Значение, передаваемое функции, эквивалентно значению, предоставленному поставщиком, ближайшим к этому контексту вверх по дереву компонентов. Если соответствующего поставщика нет, параметр value эквивалентен значению по умолчанию, переданному в createContext().
Объект контекста принимает свойство с именем displayName, имеющее строковый тип. React DevTools использует эту строку, чтобы определить, какой контекст отображать.
Следующий компонент появится как MyDisplayName в DevTools.
const MyContext = React.createContext(/* некоторое значение */);MyContext.displayName = 'MyDisplayName';<MyContext.Provider> // «MyDisplayName.Provider» в DevTools <MyContext.Consumer> // «MyDisplayName.Consumer» In Инструменты разработчикаВ приведенном выше примере темы более сложное использование может быть достигнуто с использованием динамических значений.
тема-context.js
Export const themes = { светлый: { передний план: '#000000', фон: '#eeeeee', }, темный: { передний план: '#ffffff', фон: '#222222', },};export const ThemeContext = React .createContext(themes.dark); // Это значение по умолчанию;тематическая кнопка.js
import { ThemeContext } from './theme-context'; class ThemedButton расширяет React.Component { render() { let props = this.props; // Получаем значение по умолчанию в ThemeContext let theme = this.context return ( < button; {...props} style={{backgroundColor: theme.background}} /> } // static contextType = ThemeContext;}ThemedButton.contextType = ThemeContext;export default ThemedButton;приложение.js
import { ThemeContext, themes } from './theme-context';import ThemedButton from './themed-button';// Функция промежуточного компонента с использованием ThemedButton Toolbar(props) { return ( <ThemedButton onClick={props.changeTheme } > Изменить тему </ThemedButton> );} class App расширяет React.Component {constructor(props) { super(props); this.state = {theme: themes.light, }; this.toggleTheme = () => { this .setState(state => ({ theme: state.theme === themes.dark ? themes.light : themes.dark, })); } render() { // Используется компонентом кнопки ThemedButton внутри ThemeProvider Тема значение в состоянии, // и внешние компоненты используют возвращаемое значение темы по умолчанию ( <Page> <ThemeContext.Provider value={this.state.theme}> <ToolbarchangeTheme={this.toggleTheme} /> </ThemeContext.Provider> <Section> <ThemedButton /> </Section> </Page> ); }}ReactDOM.render(<App />, document.root);// Компоненты, обернутые ThemeContext.Provider, могут использовать ThemeContext Значение // на панели инструментов и ThemedButton могут использовать this.context для получения значения // Обратите внимание, что метод обновления состояния заключается в передаче его через реквизиты, а обновление инициируется компонентами-потомками. Метод через контекст будет обсуждаться ниже.В приведенном выше примере мы передаем функцию обновления через реквизиты, чтобы изменить значение тем в приложении. Мы знаем, что необходимо обновить контекст компонента, который глубоко вложен в дерево компонентов. В этом сценарии вы можете передать функцию через контекст, чтобы компонент потребителей обновил контекст.
тема-context.js
// Убедитесь, что структура данных значения по умолчанию, переданная в createContext, соответствует вызывающим компонентам (потребителям)! Export const ThemeContext = React.createContext({ theme: themes.dark, toggleTheme: () => {}, // Определяем метод обновления темы, передаем его});тема-toggler-button.js
import { ThemeContext } from './theme-context';function ThemeTogglerButton() { // Кнопка Theme Toggler не только получает значение темы, но также получает функцию toggleTheme из контекста (часть app.js ниже) return ( < ThemeContext.Consumer> {({theme, toggleTheme}) => ( <button onClick={toggleTheme} style={{backgroundColor: theme.background}}> Переключить тему </button> )} </ThemeContext.Consumer> ); }экспортировать ThemeTogglerButton по умолчанию;приложение.js
import { ThemeContext, themes } from './theme-context';import ThemeTogglerButton from './theme-toggler-button';class App расширяет React.Component {structor(props) { super(props); ) => { this.setState(state => ({ theme: state.theme === themes.dark ? themes.light : themes.dark, })); // Состояние также содержит функцию обновления, поэтому оно Будет передан поставщику контекста. this.state = { theme: themes.light, toggleTheme: this.toggleTheme, // Определите функцию обновления и передайте ее через контекст }; } render() { // Все состояние передается в возврат поставщика ( < ThemeContext.Provider value={this.state}> <Content /> </ThemeContext.Provider> ); }}function Content() { return ( <p> <ThemeTogglerButton /> </p> );}ReactDOM.render( <App />, document.root);Чтобы обеспечить быструю повторную отрисовку контекста, React необходимо сделать контекст каждого потребительского компонента отдельным узлом в дереве компонентов.
// Контекст темы, тема по умолчанию — «light» value const ThemeContext = React.createContext('light'); // Контекст входа пользователя const UserContext = React.createContext({ name: 'Guest',}); React .Component { render() { const {signedInUser, theme } = this.props; // Компонент приложения, который возвращает исходное значение контекста ( <ThemeContext.Provider value={theme}> <UserContext.Provider value={signedInUser} > < Layout /> </UserContext.Provider> </ThemeContext.Provider> ); }}function Layout() { return ( <p> <Sidebar /> <Content /> </p> );}// Компонент может использовать несколько контекстных функций Content() { return ( <ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext . Consumer> )} </ThemeContext.Consumer> );}Если два или более значений контекста часто используются вместе, вы можете рассмотреть возможность создания собственного компонента рендеринга для предоставления этих значений.
Поскольку контекст использует ссылочный идентификатор, чтобы решить, когда выполнять отрисовку, могут возникнуть некоторые ловушки, которые могут вызвать неожиданную отрисовку в компоненте-потребителе, когда родительский компонент поставщика выполняет повторную отрисовку. Например, каждый раз при повторной визуализации поставщика следующий код будет повторно отображать все следующие потребительские компоненты, поскольку атрибут значения всегда присваивается новому объекту.
class App расширяет React.Component { render() { return ( <MyContext.Provider value={{something: 'something'}}> <Toolbar /> </MyContext.Provider> }};Чтобы предотвратить эту ситуацию, состояние значения повышается до состояния родительского узла.
class App расширяет React.Component {structor(props) { super(props); // После нескольких рендерингов состояние будет сохранено. Если значение не изменится, следующие потребительские компоненты не будут повторно отображать this.state = {. value: { Something: 'something'}, }; } render() { return ( <Provider value={this.state.value}> <Toolbar /> </Provider> }};