В React нет аналога Redux, ближайший называется Context. Многие из нас утверждают, что контекст является локальным по сравнению с Redux, который является довольно глобальным для сайта, особенно когда речь идет о том, где хранятся данные.

Однако контекст React не задуман как локальная переменная в том же смысле, что и в волокне, или хуке, или чем-либо, локализованном для единицы работы. Напротив, ничего подобного. Итак, ниже мы объясним почему.

Создать контекст

Контекст создается функцией createContext,

const UserContext = createContext(defaultValue)
export default UserContext

Объект контекста - это элемент, который содержит значение,

{
  _currentValue: defaultValue,
  Provider: { ... },
  Consumer: { ... }
}

Контекст, такой как UserContext, экспортируется сразу, потому что на него должен ссылаться каждый поставщик и потребитель. Помещение их в один файл не раскрывает его природу. Контекст должен быть предоставлен как потребителям, так и поставщикам!

Область контекста

Прежде чем говорить об области контекста, давайте посмотрим, что такое область действия функции.

function provider1(value) {
  const provider2 = (value) => {
    const provider3 = (value) => {
      console.log(value)  
    }
    provider3(3)
  }
  provider2(2)
}
provider1(1)

Угадайте, что выводится в приведенном выше коде. Ответ 3. Потому что все мы знаем, что функция имеет область видимости. В области действия функции приоритет имеет локальная переменная из входного аргумента.

Провайдер контекста разработан аналогичным образом: он использует стек для проталкивания старого значения перед входом во внутреннюю область видимости, а затем выталкивает старое значение после выхода из области видимости.

const Branch = ({ theme1, theme2 }) => {
  return (
    <ThemeContext.Provider value={theme1}>
      // A. value = theme1
      <ThemeContext.Provider value={theme2}>
        // B. value = theme2
      </ThemeContext.Provider>
      // C. value = theme1
    </ThemeContext.Provider> 
    // D. value = defaultValue
  )
}

Вы когда-нибудь задумывались, учитывая theme1 и theme2, почему значения контекста в местоположениях A, B и C различаются? Как может одна переменная _currentValue под тем же ThemeContext содержать динамическое значение? Как это возможно? Что такое _currentValue? Не theme1, не theme2?

Не похоже, что приведенный выше код может работать ни с одним из упомянутых локальных значений в коде.

Стек и курсор

Оказывается, за сценой работает глобальная переменная, назовите ее cursor. Прежде чем войти во внутреннюю область видимости с value, мы помещаем ее в стек.

function push(cursor, value) {
  index++
  
  valuesStack[index] = cursor.current
  
  cursor.current = value
}

Операция push помещает старое значение в стек, а затем заменяет текущее значение cursor новым value. И после того, как мы закончим внутреннюю область видимости, мы извлекаем ее из стека.

function pop(cursor) {
  if (index < 0) return
  
  cursor.current = valueStack[index]
  valueStack[index] = null
  index--
}

Таким образом, cursor всегда указывает на последнее значение относительно области видимости. И стек используется для того, чтобы убедиться, что он сможет найти путь обратно.

Поставщик push and pop

Так работают поставщики, когда они вложены. Когда мы обновляем поставщика, он его вставляет.

function updateContextProvider(current, workInProgress) {
  ...
  pushProvider(workInProgress, newValue)
  if (oldProps !== null) { ... }
  ...
}
function pushProvider(providerFiber, nextValue) {
  var context = providerFiber.type._context
  push(valueCursor, context._currentValue)
  context._currentValue = nextValue;
}

Функция pushProvider устанавливает _currentValue контекста в newValue через опору value. Вот как потребители во внутренней области видимости получают внутреннюю ценность. А перед этим вы видите, как он push старый _currentValue в стек и сохраняет его вместе с глобальной переменной valueCursor.

function popProvider(providerFiber) {
  var currentValue = valueCursor.current
  pop(valueCursor)
  var context = providerFiber.type._context
  context._currentValue = currentValue
}

Когда провайдер завершает работу в completeWork, он вызывает popProvider, который устанавливает currentValue контекста обратно на сохраненное значение из valueCursor. А перед этим вы видите, как pop старое значение из стека.

Резюме

Всегда существует только один контекст, такой как объект UserContext, поэтому _currentValue действительно содержит «глобальную» переменную и переключается только для хранения значения локальной области (через свойство value), когда он входит в область каждого поставщика, потому что потребителю необходимо читать из файла _currentValue.

Однако, если вы хотите найти все значения контекста, соответствующие всем потребителям (подключенным к нескольким областям), единственное место, которое вы можете найти, - это стек, который содержит список значений контекста в каждой вложенной области.

Поэтому вы можете утверждать, что значение контекста становится включено в настройках локального провайдера в нужной области, но мы должны признать, что значение контекста, такое как _currentValue, единственное хранилище данных контекста, является глобальным переменный характер.

Приложение

Почему нас волнует, является ли контекст глобальной или локальной переменной? Это имеет значение, если вы все еще думаете, что контекст не может делать то, что предоставляет redux. Потому что теоретически, основываясь на описанной выше схеме, контекст может делать что угодно, поскольку он не связан с каким-либо волокном, это глобальная общая переменная. На самом деле, даже лучше, когда контекст может переключаться на версию с локальной областью видимости, когда он входит в область действия поставщика, это должно сделать его более универсальным, чем то, что предоставляет Redux.

В прошлом проблема заключалась в том, как правильно использовать React.memo с контекстом. Потому что без memo контекст может стать смертельным оружием. Поэтому, чтобы избежать этих проблем, необходимо улучшить контекст, по крайней мере, должно быть очень ясно, как обновить конкретное значение, не допуская чрезмерного рендеринга. Возможно, это не проблема контекста, потому что Redux также часто получает чрезмерные рендеры.

Во время написания этой статьи React предлагает useContextSelector, что, кажется, направляет его в правильное русло. Вот предложение.

Больше контента на plainenglish.io