React, вероятно, самая популярная фреймворк / библиотека внешнего интерфейса. Существует множество руководств и блогов, показывающих, как использовать эти концепции / терминологию.
Но задумывались ли вы, почему / как Redux использует Provider для предоставления «хранилища» дочерним компонентам и как «подключаются» к хранилищу, которое вы предоставили?
Если вы используете React Context для замены Redux для «управления состояниями поддерева», заметили ли вы, что Redux внутренне также использует Context?
Давайте углубимся в это и, надеюсь, некоторые из моих выводов помогут вам получить лучшее представление.
Во-первых, вся эта махинация - это не что иное, как способ разделить составляющие «государственной» суммы. Как обычно можно использовать переменную в файлах Javascript? Ответ - паттерн «одноэлементный».
Javascript не похож на Java или C #, вам не нужно использовать их шаблон Singleton, который использует статическую переменную в классе. В Javascript есть простой способ, при котором каждая локальная переменная в функции сохраняется, если вы держите на нее ссылку.
Например, предположим, что у нас есть простой файл my-context.js вроде этого:
function createContext(value){ return { store: value } } let MyContext = createContext(null) export default MyContext;
Если вы импортируете его во все другие файлы Javascript с помощью
import MyContext from "./my-context.js"
Только первый import
запускает createContext(null)
строку. Все остальные файлы, которые импортируют ./my-context.js
, просто получат уже созданный экземпляр.
Так почти все JS-фреймворки используют для обмена данными.
Вернемся к React, если вы хотите поделиться своим общим состоянием, скажем, ваш Redux store.
Вы можете догадаться, что решение - это файл Singleton, подобный my-context.js
выше.
Если вы посмотрите Исходный код React-Redux (./Context.js)
Это точно так.
import React from 'react' export const ReactReduxContext = React.createContext(null) export default ReactReduxContext
Провайдер берет ваш магазин в свой реквизит и сохраняет его в контексте выше.
import { ReactReduxContext } from './Context' ... render() { const Context = this.props.context || ReactReduxContext return ( <Context.Provider value={this.state}> {this.props.children} </Context.Provider> ) }
Теперь ваши компоненты, которые хотят использовать Reduxstore
, должны будут использовать функцию connect
для подключения к. Как? просто импортируйте файл контекста, как мы это делали в нашем my-context.js
примере выше
import { ReactReduxContext } from './Context' ... const ContextToUse = useMemo(() => { // Users may optionally pass in a custom context instance to use instead of our ReactReduxContext. // Memoize the check that determines which context instance we should use. ... } // Retrieve the store and ancestor subscription via context, if available const contextValue = useContext(ContextToUse) const store = props.store || contextValue.store
По сути, он получает общий контекст, который содержит нашу переменную store
из этого файла singleton ./Context.js, и проверяет, передаете ли вы также переменную store в его реквизитах, если вы этого не сделали, тогда просто используйте общий контекст singleton. store, сохраненный Поставщиком выше.
Теперь совершенно ясно, как React Redux, Context и Hooks творит чудеса: просто простой файл контекста синглтона :-)
Разве это не ./Context.js над глобальным синглтоном, и как я могу иметь несколько экземпляров хранилища или контекста? Хороший вопрос, в React 5.x вы можете использовать storeKey в createStore, это довольно простая идея, вы просто сохраняете карту контекста, основанную на storeKey, а не на отдельном хранилище.
Эта функция была удалена в новейшей версии React (7.x), но вы все равно можете передавать другой контекст Provider и Connect с помощью их свойств. Вам просто нужно создать контекст самостоятельно
Хуки - самая популярная новинка в React. Одним из распространенных заблуждений является то, что хуки собираются упростить или заменить контекст и Redux. Фактически, хуки предназначены только для того, чтобы дать компоненту без сохранения состояния те же функции, что и компоненты с сохранением состояния, без преобразования их в компонент с сохранением состояния. Они также используют Context для «совместного использования состояния».
Контекст по-прежнему является решением для хуков делиться своими состояниями. Таким образом, понимание того, как использовать Context, принесет пользу как старым, так и новым методам, которые использует команда React.
Есть 2 распространенных шаблона использования контекста.
Первый способ - использовать this.context и contextType дочернего компонента.
1. Создайте файл для createContext
theme-context.js
export const themes = { light: { foreground: '#000000', background: '#eeeeee', }, dark: { foreground: '#ffffff', background: '#222222', }, }; export const ThemeContext = React.createContext( themes.dark // default value );
2. Дочерний компонент устанавливает contextType в контекст, созданный выше, и использует this.context для доступа к объекту контекста
themed-button.js
import {ThemeContext} from './theme-context'; class ThemedButton extends React.Component { render() { let props = this.props; let theme = this.context; return ( <button {...props} style={{backgroundColor: theme.background}} /> ); } } ThemedButton.contextType = ThemeContext; export default ThemedButton;
3 Оберните дочерний компонент в ThemeContext.Provider
render() { return ( <Page> <ThemeContext.Provider value={this.state.theme}> <ThemedButton onClick={this.toggleTheme}> Change Theme </ThemedButton> </ThemeContext.Provider> <Section> <ThemedButton /> </Section> </Page> ); }
Второй способ: использовать ThemContext.Consumer
1. Создайте файл для createContext
тема-context.js
// Make sure the shape of the default value passed to // createContext matches the shape that the consumers expect! export const ThemeContext = React.createContext({ theme: themes.dark, toggleTheme: () => {}, });
2. Создайте дочерний компонент-потребитель
theme-toggler-button.js
Обратите внимание на ({them, toggleThemes}) = ›{..}
Это соответствует структуре объекта, которую мы создалиContext (..) выше.
import {ThemeContext} from './theme-context'; function ThemeTogglerButton() { // The Theme Toggler Button receives not only the theme // but also a toggleTheme function from the context return ( <ThemeContext.Consumer> {({theme, toggleTheme}) => ( <button onClick={toggleTheme} style={{backgroundColor: theme.background}}> Toggle Theme </button> )} // This block has to be enclosed by { } </ThemeContext.Consumer> ); } export default ThemeTogglerButton;
3 Оберните дочерний компонент поставщиком
render() { // The entire state is passed to the provider return ( <ThemeContext.Provider value={this.state}> <ThemeTogglerButton /> </ThemeContext.Provider> ); }
Как только мы поймем, что ключевым моментом является файл контекста, совместно используемый компонентами, нам будет намного легче понять, почему и как использовать Context.Provider и Consumer или this. контекст.
Если вы обратите внимание на онлайн-руководства по React Context, многие из них копируют / вставляют друг друга и помещают createContext
, Provider
и Consumer
в один файл / место. Это завершенное поражает цель Context. Если вы можете поместить все 3 из них в один файл, почему бы просто не передать общее состояние / объект с помощью props
?
Кстати, и последнее, но не менее важное: вот эталонная реализация createContext
Реализация CreateContext Polyfill
Код довольно прост, createContext генерирует 2 функции, одна - Provider, а другая - Consumer. Внутри он использует EventEmitter, который представляет собой просто массив подписанных на него обработчиков обратного вызова. Провайдер генерирует событие изменения, а Потребитель обрабатывает его с помощью OnUpdate и запускает рендеринг.
render() { return onlyChild(this.props.children)(this.state.value); }
Вот почему шаблон Context.Consumer выглядит так, как показано ниже. Значение уменьшается функцией Consumer в контексте.
<MyContext.Consumer> { (value) => { ... } } </MyContext.Consumer>
На самом деле нет ничего волшебного. :)
Надеюсь, это поможет вам понять основную концепцию React и лучше ее понять. Удачного взлома