Какое решение государственного управления подходит для вашего проекта?

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

К счастью, есть. Как разработчик JavaScript я понял, что нас не устраивает только одно. Нам нужны альтернативы. Поэтому React 16.4 предложил нам Context API.

Эта статья поможет вам разобраться и ответить на самые распространенные вопросы об управлении состоянием:

  • Почему мы не можем просто использовать свойства для управления состоянием (цепочкой свойств)?
  • Что такое Redux и почему мы его используем?
  • Как мы можем настроить Redux для нашего приложения?
  • Что такое контекстные API?
  • Как мы можем настроить контекстные API с помощью хуков?
  • И наконец, какой вариант лучше?

Примечание. Если вы уже знаете, как работает Redux и что такое цепочка опор, вы можете перейти к разделу API контекста.

Я начну с демонстрационного приложения, которое просто отображает нескольких пользователей, а также их имена и статус аутентификации. Для этого у нас будет три основных компонента: компонент приложения, компонент пользователей и компонент пользователя.

Компонент приложения

Этот компонент (контейнер) будет иметь доступ к состоянию. В нашем состоянии будет несколько вещей: массив users и еще одно логическое поле для authentication.

У нас будет кнопка Войти в систему внутри App.js. При щелчке он выполнит вход всех пользователей (изменит их статус аутентификации на true). Затем мы передадим пользователей и isAuthenticated flag через реквизиты в Users.js.

Вот как должен выглядеть наш компонент приложения:

Компонент "Пользователи"

Этот компонент будет компонентом без состояния. Здесь мы будем отображать только пользователей, приходящих из App.js, и передадим значения в качестве свойств User.js:

Пользовательский компонент

User.js просто отображает данные, полученные как реквизиты от Users.js:

Это очень простое приложение. Теперь, когда мы нажимаем кнопку Login User, мы меняем статус аутентификации всех трех пользователей на true.

Так в чем проблема в этом приложении?

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

В чем конкретно проблема цепи опоры?

В нашем приложении мы передаем isAuthenticated flag как опору в Users.js, а затем снова как опору в User.js.

Но нужно ли нам вообще значение isAuthenticated in Users.js? Компонент Пользователи пересылает только значение isAuthenticated. Он действительно получает ценность как опора, но на самом деле это не волнует. Это не так уж и здорово, поскольку это просто приводит к дополнительной избыточности и делает наш компонент менее пригодным для повторного использования, поскольку нам всегда нужно передавать ему значение isAuthenticated , где бы мы ни использовали компонент Users.

Redux спешит на помощь

Как упоминалось ранее, Redux - это библиотека JavaScript для управления состоянием приложения.

Я попытаюсь объяснить, как работает Redux, и после этого вы, возможно, поймете, как он может решить проблему с нашей цепочкой опор.

Итак, Redux работает вокруг идеи иметь центральное состояние, называемое хранилищем. Чтобы изменить состояние, компонент должен отправить действие. Затем действие передается редуктору, который изменяет состояние нашего приложения.

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

  • Первым шагом здесь является установка redux и react-redux в наше приложение.
  • Второй шаг - добавить наш магазин. Магазин должен быть создан непосредственно перед запуском нашего приложения, поэтому index.js будет хорошим местом для добавления нашего магазина. Так что будем import { createStrore } from ‘redux’. Затем это хранилище необходимо сохранить в постоянной переменной. Назовем его store, где мы вызовем функцию createStore и передадим наш редуктор.
  • Поскольку нам нужно передать наш редуктор, нам нужно будет создать файл для этого редуктора и импортировать этот файл в наш index.js. Как будет выглядеть наш файл-редуктор? Как упоминалось ранее, изменение состояния может происходить только через редуктор. Итак, у нас будет начальное состояние в нашем reducer.js. Функция reducer имеет два параметра: состояние и действие (которое она получит) из отправленного действия из компонента.
  • Наш редуктор в настоящее время просто возвращает состояние. Теперь мы можем импортировать этот редуктор в наш index.js и передать этот редуктор функции createStore (const store = createStore(reducer)). Теперь у нас есть наш магазин. Чтобы подключить этот магазин к нашему приложению React, нам понадобится специальный пакет, который мы уже установили (react-redux). После установки нам потребуется import { Provider } from ‘react-redux’. Этот провайдер должен обернуть наш компонент приложения. Поставщик - это вспомогательный компонент, который позволяет нам внедрить наш магазин в наше приложение.
  • Теперь, как нам получить данные из магазина? Что ж, для этого нам нужно подключить App.js к магазину. Для этого мы используем нечто под названием connect, которое предоставляется пакетом react-redux. connect - это функция высшего порядка, которую мы используем в нашем App.js экспорте. Однако connect сама по себе является функцией, которая возвращает функцию, которая затем принимает компонент в качестве входных данных (export default connect()(App);). Теперь для первого выполнения функции connect мы можем передать некоторую конфигурацию. Точнее, мы передаем две части информации. Во-первых, часть всего приложения заявляет, что нам нужно в нашем компоненте. Во-вторых, действия, которые нам нужно отправить из компонента. Эти две конфигурации имеют имена (не обязательно с одинаковыми именами, но это наиболее распространенное соглашение об именах) mapStateToProps и mapDispatchToProps. Теперь нам не нужно наше локальное состояние, которое мы использовали ранее в нашем App.js, и мы также можем избавиться от loginHandler, поскольку мы будем обрабатывать функции входа в систему в нашем редукторе, который, в свою очередь, отправляется как действие из нашего App.js. Теперь значение состояния может быть доступно внутри нашего компонента с помощью this.props.
  • Наконец, наш редуктор теперь будет возвращать обновленное состояние, когда мы нажимаем кнопку Login Users.
  • Теперь, когда мы настроили Redux в нашем приложении, последнее, что нужно сделать, это передать значение isAuthentication flag из нашего центрального состояния непосредственно в User.js , а не через Users.js. Как этого добиться? Это просто. Поскольку каждый компонент теперь может напрямую обращаться к состоянию (используя connect), мы можем напрямую обращаться к значению состояния в нашем User.js. Попробуйте дать ему шанс, но ниже есть ссылка, если вы не можете это сделать:

Таким образом, Redux помогает нам решить проблему цепочки опор. Это может не иметь отношения к такому маленькому приложению, но значительно упрощает нашу работу при использовании Redux в более крупных приложениях.

Контекстные API и хуки useContext

Наконец, мы подошли к самой интересной части - Context API. Во-первых, давайте выясним, что это такое.

Контекстные API-интерфейсы появляются, когда нам нужны определенные данные из определенного состояния в нескольких компонентах, но мы не хотим передавать данные по нескольким уровням.

Для этого мы создадим новый файл auth-context.js. В этом файле мы создадим объект контекста. Доступ к нему предоставит React.

const authContext = React.createContext(); 

createContext позволяет нам инициализировать наш контекст значениями по умолчанию. Этот createContext является объектом JavaScript (технически он также может быть массивом, строкой, числом и т. Д.), Который может передаваться между различными компонентами. Здесь. мы можем передать значение по умолчанию isAuthenticated.

Примечание. Мы добавляем это для лучшего автозаполнения в нашем проекте.

import React from 'react';
const authContext = React.createContext({
    isAuthenticated: false,
});
export default authContext;

Теперь мы можем импортировать этот контекст в любое место нашего приложения, и он должен обернуть все части вашего приложения, которым необходим доступ к нему. Нам нужно будет обернуть это, используя имя импорта context и добавив после него .Provider.

Вот как должна выглядеть наша функция рендеринга App.js:

Теперь мы оборачиваем наш AuthContext внутри нашего Users компонента. Затем мы можем получить доступ к значению isAuthenticated в нашем Users компоненте и всех его дочерних компонентах (в нашем случае User.js).

Этого можно добиться тремя способами.

Использование Consumer

Для этого мы можем напрямую перейти к нашему User.js. Внутри нашей функции рендеринга мы можем добавить AuthContext.Consumer. Не забудьте импортировать AuthContext в область, где нам нужен доступ к флагу isAuthenticated. Вот так теперь будет выглядеть наша функция рендеринга. Теперь этот AuthContext.Consumer - наш JSX компонент. Поскольку это какое-то выражение JavaScript, нам нужно заключить его в фигурные скобки. Единственная проблема, которую вы должны иметь в виду, это то, что Consumer принимает функцию как дочернюю между открывающим и закрывающим тегами, и эта функция будет принимать наш объект context. Наконец, чтобы получить isAuthenticated flag, мы можем получить его через context.

Использование contextType

Проблема использования Consumer заключается в том, что он работает только в функции рендеринга, но есть большая вероятность, что нам понадобится значение состояния в других частях нашего компонента (например, componentDidMount()). Там у нас не будет доступа к нашему контексту.
Чтобы решить эту проблему, React 16.6 представил нам специальную статическую опору contextType, которую можно использовать в компоненте на основе классов (static contextType = AuthContext). Теперь это позволяет React автоматически связывать этот компонент с нашим внутренним контекстом. Это дает нам новое свойство, которое мы можем использовать в любой части нашего приложения: this.context.
Чтобы получить статус аутентификации, мы просто используем:

<p>AuthenticationStatus:this.context.isAuthenticated.toString()}</p>

Использование useContext Hook

Наконец, чтобы использовать это в компоненте функции , мы можем использовать ловушку, предоставленную React, useContext.

const authContext = useContext(AuthContext);

Теперь мы можем использовать этот authContext в любом месте нашего функционального компонента, чтобы получить доступ к isAuthenticated flag.

<p>AuthenticationStatus: authContext.isAuthenticated.toString()}</p>

Вот и все. Теперь мы узнали, как управлять своим состоянием с помощью API контекста.

Что лучше?

Теперь, когда мы узнали, как работают API Redux и Context, мы можем сравнить их и понять их плюсы и минусы.

  • Размер пакета: единственное, что мы должны помнить, это то, что Redux - это библиотека третьего пакета, которая не является частью React, и, следовательно, нам нужно установить зависимости - в основном три из них (redux, react-redux, redux -thunk). Использование Redux требует затрат. Установка этих зависимостей увеличивает размер окончательного пакета. Напротив, контекстные API являются частью React, поэтому размер нашего пакета остается прежним.
  • Шаблонный код: с Redux нам нужна исчерпывающая настройка, нам нужно создать магазин и нам нужно отправлять действия. Затем нам нужно подключить наш магазин к нашим компонентам. Иногда это неприятно для разработчика. Велика вероятность того, что можно потеряться в кодах и просто бесцельно блуждать, не зная, как что-то исправить. По сути, вам нужен практический подход к работе с Redux. По моему мнению, контекстные API содержат меньше шаблонных кодов. С появлением React 16.6 нам даже не нужен потребитель. С помощью всего одной строчки кода вы можете получить доступ к своему контексту.
  • Обработка асинхронного кода: в контекстных API запуск API (асинхронные коды) относительно прост в использовании, как только вы его освоите (особенно при использовании хуков). Вам также не нужен такой пакет, как redux-thunk, для обработки асинхронных действий.

Заключение

Из этой статьи вы можете догадаться, почему API-интерфейсы контекста могут быть лучше, чем Redux. Однако ожидается, что будет выпущена более новая версия Redux. Он будет содержать меньше шаблонного кода и будет более эффективным. Подождем, чтобы увидеть, как это работает.