Поговорим о важных и популярных сторонних библиотеках для React: Redux и Router. Зачем они нужны и как можно применить эти библиотеки на практике. Расскажу на примере калькулятора, который мы создавали в предыдущих частях цикла.
Предыдыдущая часть: Калькулятор-конвертер на базе React. Часть 8: Конвертер валют и собственный парсер данных
Что такое Redux
Redux – это сторонняя библиотека для управления состояниями JavaScript-приложений. Вы уже знаете, как работают состояния компонентов в Реакте. Так вот Redux позволяет эти состояния перенести из кучи разных элементов в единую систему контроля, находящуюся обособленно от остальных файлов проекта, но при этом всегда в зоне досягаемости. То есть состояние множества компонентов может содержаться в едином хранилище. Его можно обновлять из любого Реакт-компонента и отображать в любом Реакт-компоненте.
Redux позволяет упростить запутанные структуры передачи данных от родительских компонентов к наследникам и от наследников обратно к родителям.
Трудно сказать, насколько целесообразно использование Redux в нашем приложении. Об эту тему было сломано уже немало копий, и я не буду пересказывать статьи на тему того, как важно применять те или иные технологии только в тех ситуациях, когда они действительно нужны. Я лишь продемонстрирую базовый пример работы Redux, а вы уже сами решите, нужен ли он вам здесь. Либо вы просто обучитесь работе с Redux и сможете применять его в других приложениях.
Устанавливаем Redux
Сначала нужно загрузить саму утилиту как npm-пакет и вместе с ней подтянуть все зависимые пакеты, чтобы полностью настроить базовый вариант хранилища состояний.
-
Открываем терминал в директории с нашим проектом.
-
Вводим команду npm install react-redux
-
Затем вводим npm install @reduxjs/toolkit
По окончании работы обеих команд можно переходить к настройке непосредственно в приложении.
Базовая настройка Redux
Для начала надо определиться с тем, как вообще будет использоваться Redux и для чего он нам нужен. Так как количество сценариев применения Redux ограничивается лишь фантазией программистов, то конкретные причины применения Redux никто назвать не сможет.
В нашем случае Redux будет хранить в себе историю любых вычислений. Будь-то подсчеты в калькуляторе или какие-то операции, выполненные конвертером. То есть наше поле History превратится в общедоступное пространство, в которое можно перенести информацию из любого режима работы приложения, чтобы сохранить полученные значения.
С этой информацией в голове переходим к созданию своего первого Redux-хранилища.
Сначала создаем файл store.js и сохраняем его в нашем проекте. Это и будет JavaScript-документ, содержащий в себе состояния компонентов.
В качестве содержимого файла store.js нужно указать базовую конфигурацию Redux с ссылками на сопутствующие компоненты и с объектом, хранящим в себе состояния компонентов.
-
Импортируем конфигурационный файл configureStore.
import { configureStore } from '@reduxjs/toolkit'
-
Затем импортируем функцию изменения состояния с помощью Redux (мы создадим ее чуть позже).
import historyReducer from './historySlice'
-
После этого экспортируем уже измененный объект configureStore во внешние пространства (чтобы к нему можно было обратиться из сторонних компонентов приложения). Внутри него укажем функцию-reducer, отвечающую за изменение Redux-состояния.
export default configureStore({ reducer: { history: historyReducer, }, })
Закончив с хранилищем, переходим к созданию метода для изменения состояния. Он будет содержаться в файле historySlice.js, который мы создадим в корневой директории проекта.
А вот его содержимое:
-
Импорт функции createSlice из пакета @reduxjs/toolkit:
import { createSlice } from '@reduxjs/toolkit'
-
Создание переменной, содержащей в себе вызов функции createSlice с необходимыми данными:
export const historySlice = createSlice( )
-
В качестве аргумента для createSlice выступит объект со всей необходимой информацией и методами для управления состояниями:
-
Имя – name: history
-
Состояние по умолчанию – initialState: { value: [], }
-
Методы управления состоянием (функция-reducer). В нашем случае это функция updateHistory, принимающая аргументы из сторонних компонентов при вызове и обрабатывающая их внутри Redux.
-
updateHistory: (state, action) => state.value.push(action.payload) }
Объявив каждый элемент и описав метод управления состоянием, остается экспортировать это все во внешнюю среду, чтобы потом импортировать в программу.
-
Экспортируем метод.
export const { historyUpdate } = historySlice.actions
-
Экспортируем состояние.
export const historyState = state = state.history.value
-
Экспортируем метод reducer целиком.
export default historySlice.reducer
Готово. Можно переходить ко второй части.
Подключаем Redux к нашему приложению
У нас есть внешнее состояние, но на текущий момент оно никак не используется в нашем приложении. Мы будем изменять его из двух компонентов нашей программы, а отображаться оно будет в общей части интерфейса, видимой при использовании и калькулятора, и конвертера.
Для работы с Redux-состояниями в React-приложении, надо импортировать хранилище store и компонент Provider в корневой файл программы (в нашем случае это index.js).
Получится:
import store from './components/store' import { Provider } from 'react-redux'
Provider – это родительский компонент-обертка, в который нужно поместить весь код приложения целиком. Ранее мы создавали ChakraProvider. Так что надо и его поместить внутрь Provider от Redux. Также важно не забыть передать store в качестве аргумента для Provider.
<Provider store={store}> </Provider>
Для начала добавим наше внешнее состояние в компонент Converter, чтобы опробовать его в полной мере и понять, работает ли оно вообще.
Импортируем весь список нужных нам элементов.
-
Хуки из библиотеки Redux:
import { useSelector, useDispatch } from 'react-redux'
-
Методы и состояние из нашего файла historySlice:
import { updateHistory, historyState } from '../historySlice
Далее создаем для них переменные внутри компонента Converter. Переменная history должна использовать хук useSelector на состоянии history, чтобы хранить его в компоненте, а переменная dispatch позволяет быстро обратиться к хуку useDispatch().
Дальше дело техники. Чтобы отобразить в интерфейсе состояние history, мы просто записываем соответствующую переменную в методе return компонента Converter. А чтобы менять его, создадим отдельную клавишу, активирующую метод dispatch.
В нашем случае нужно передавать в Redux запрос на запуск метода updateHistory с аргументом result (так как мы планируем передавать значение вычисления в конвертере).
<Button onClick={() => dispatch(updateHistory(result))}
Получится подобный интерфейс. Он показывает результат преобразования одной единицы измерения в другую и кнопку отправки этого значения в состояние history.
При нажатии на эту кнопку результат конвертации попадет в Redux-состояние history и отобразится выше.
Проблема заключается в том, что мы не получаем преимуществ от Redux, так как history не отображается в других элементах. Нам нужно само состояние показать в интерфейсе на более высоком уровне, чтобы мы могли лицезреть его всегда, а не только при работе с конвертером. Ведь по изначальной задумке и функция изменения состояния должна работать из нескольких разных компонентов.
Поэтому мы импортируем все те же данные из Redux и historySlice, но уже в компонент App. Там создадим переменную history, обращающуюся к хуку useSelector, и вернем ее в return.
Теперь можно добавить кнопку Add to history в любой компонент приложения, в том числе и в калькулятор. Работать она будет так же, как и любые другие аналогичные кнопки в приложении.
Можно слегка модифицировать содержимое переменной history в компоненте App, чтобы элементы состояния отображались в отдельных кнопках.
Получится вот такой интерфейс.
Также в нашем приложении Redux может упростить процесс реализации Drag & Drop.
Что такое Router
Router – это популярный инструмент для Реакт-программ и один из важнейших компонентов любого веб-приложения, позволяющий связать отдельные компоненты вашего проекта с конкретными адресами в браузере.
Каждый компонент для браузера выглядит именно как отдельная страница, что позволяет оставлять ссылки на отдельные компоненты приложения. Например, в нашем случае вы можете создать ссылку сразу на калькулятор, а не на конвертер (или наоборот). Также это поможет сохранять состояние приложения при его перезагрузке. Сейчас при нажатии F5 мы автоматически переключаемся на калькулятор.
Устанавливаем Router
Мы будем использовать React-Router. Есть и другие варианты роутинга, но этот показался мне наиболее простым. С ним легче всего разобраться, да и процесс установки укладывается в одну команду.
-
Открываем терминал в директории с нашим проектом.
-
Вводим команду npm install react-router-dom@6
По завершении работы команды переходим к настройки роутинга в приложении.
Подключаем Router к нашему приложению
Подключается роутер довольно легко, так же, как и другие сторонние библиотеки. Нужно просто обернуть приложение в компонент, идущий в комплекте с React Router.
Импортируем нужный компонент в index.js.
import { BrowserRouter } from 'react-router-dom'
Теперь оборачиваем нашу программу в роутер. Нужно «положить» все, что идет уровнем ниже ChakraProvider, в BrowserRouter, как показано на скриншоте.
После этого переходим к настройке роутинга в компоненте App.js. Так как это практически корень нашей программы, отсюда мы и будем перемещаться в разные части созданного нами инструмента.
Импортируем сюда три компонента, отвечающих за базовую функциональность роутера. Это блок Routes, в котором будут содержаться все пути (ссылки на отдельные компоненты приложения), блок Route, хранящий в себе отдельный компонент для рендеринга, и Link – элемент, позволяющий переключаться между разными элементами страницы.
import { Routes, Route, Link } from 'react-router-dom'
Теперь пути нужно разместить в интерфейсе. Их можно спокойной прописать в методе return того компонента, который является корневым в вашем приложении. Все сразу они отображаться не будут, потому что роутинг работает как условный рендеринг, то есть отображаемый на странице контент будет зависеть от выбранного на текущий момент пути.
-
Вместо переменной application, использованной ранее, создаем блок Router.
<Routes> </Routes>
-
Внутри блока указываем все нужные на пути, прописывая для них ссылку и компонент, который надо отобразить:
-
Путь до базового компонента (то есть того, что будет отображаться в интерфейсе программы по умолчанию). Так как у нас в программе нет главного экрана или его аналога, то мы по базовому пути будем выдавать калькулятор:
<Route path="/" element={<Calculator />} />
-
Путь до конвертера:
<Route path="converter" element={<Converter />} />
-
Путь до калькулятора:
<Route path="calculator" element={<Calculator />} />
-
Осталось лишь сделать интерфейс для переключения между путями. У нас уже есть меню, отвечающее за работу условного рендеринга (то есть то, что позволяет выбирать значение переменной application внутри компонента App). Вот его и модифицируем, заменив кнопки, меняющие значение переменной application на элементы Link, а также URL в адресной строке браузера.
-
Для калькулятора этот элемент будет выглядеть так:
<Link to="/calculator">Calculator</Link>
-
Для конвертера – так:
<Link to="/converter">Converter</Link
Вот как будет выглядеть приложение при запуске (обратите внимание на адресную строку).
И вот что будет, если переключиться на конвертер в меню.
Теперь при ручном вводе адреса /converter вы будете попадать в нужную часть программы, и это состояние приложения не будет сбрасываться при перезагрузке страницы.
Вместо заключения
Мы изучили два важных элемента React-приложений, часто используемых в проектах различных масштабов. Даже если они и не нужны в таком мелком приложении, как калькулятор, то в других веб-сервисах они точно пригодятся.
Продолжение: Калькулятор-конвертер на базе React. Часть 10: Деплой
Комментарии