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 примере выше

В коде connectComponent

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 и лучше ее понять. Удачного взлома