Руководство по Redux: пишем первое Redux-приложение

Знаете JS и React? Прочитав наше руководство, вы освоите Redux. В этом материале разбираемся, как написать Redux-приложение.

Для примера возьмём Redux-приложение «Hello World», и будем с ним работать далее.

Как устроено Redux-приложение «Hello World»

Всё построено на Bootstrap с использованием функции create-react-app, так что вы, скорее всего, знаете, как это работает. Ещё, нам пригодится вот этот репозиторий GitHub.

Итак, у нас есть файл index.js, который отображает <App /> в DOM. Этот App состоит из <HelloWorld /> и с помощью tech выполняет то, что хочет пользователь.

Если задать <HelloWorld tech="React" />, то на выходе вы получите это:

А если <HelloWorld tech="Redux" />, то это:

Посмотрите, как выглядит компонент App:

src/App.js

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

class App extends Component {
 state = { 
  tech : "React"
}
render() {
  return <HelloWorld tech={this.state.tech}/>
}
}

export default App;

Взгляните на объект state, в котором находится tech. Он переходит вниз также, как prop в компонент HelloWorld:

<HelloWorld tech={this.state.tech}/>

Пока не стоит задумываться над тем, как реализуется компонент HelloWorld. Он просто использует базу tech и добавляет всякие штуки из CSS.

Но не будем останавливаться на стилях. Сейчас вопрос в другом: как провести рефакторинг, чтобы всё нормально работало в Redux? Об этом ниже.

Вспоминаем то, что знаем о Redux

Что гласит документация? То, что Redux − предсказуемый контейнер состояния. Таковым его делает state, с его помощью приложением можно управлять. Вот так выглядит состояние для компонента <App/>:

{
 tech: "React"
}

Но нам так не надо, потому что управлять приложением мы хотим только используя Redux. Вспомните, до этого мы рассказывали, что банк хранит деньги в vault, а Redux Store проверяет и обеспечивает достоверность данных. Здесь всё так же.

И первое, что нужно сделать − удалить состояние компонента внутри <App />:

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

class App extends Component {
 // the state object has been removed. 
render() {
  return <HelloWorld tech={this.state.tech}/>
}
}

export default App;

Это ещё не всё, но <App/> уже не имеет состояния.

Установите Redux, запустив yarn install redux с помощью интерфейса командной строки (CLI).

Создаём Redux Store

Так как <App /> больше не может управлять своим состоянием, нужно настроить Redux Store для управления состоянием нашего приложения.

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

Так выглядит код store, благодаря которому мы осуществим задуманное:

import { createStore } from "redux"; // импорт из Redux-библиотеки
const store = createStore();  // пока невыполнимое действие

Для начала импортируем функцию createStore из Redux. Затем вызываем функцию createStore(), чтобы создать хранилище.

Теперь createStore принимает несколько значений. Первое − reducer.

Отношения между хранилищем и редуктором

И снова вернемся к банку. При снятии денег вы сообщаете кассиру о намерении получить их с помощью функции WITHDRAW_MONEY. И не получаете их сразу же. Потому что сначала кассир должен проверить, есть ли деньги у него и есть ли они на вашем счету. Можно считать, что кассир неотделим от хранилища, ведь если бы они не работали в связке друг с другом, денег бы вы не получили.

Redux store − это хранилище, а Redux reducer − это кассир, и они всегда должны быть синхронизированы.

Почему так? Редуктор всегда обращается к хранилищу. И хранилище всегда ждёт запроса от редуктора. Это единственный, кто может послать запрос createStore().

Далее, мы рассмотрим, как работает reducer, и попробуем связать его со store с помощью createStore.

Reducer

Официальная документация гласит: редукторы − самые важные концепты в Redux.

Иногда их называют сокращающими функциями. Возможно, вы пользовались ими, например − Array.reduce().

Посмотрим, как это работает на отрывке кода:

let arr = [1,2,3,4,5]
let sum = arr.reduce((x,y) => x + y)
console.log(sum)  //15

Это самый простой способ найти сумму чисел в массиве в JavaScript. Здесь редуктор принимает два значения − accumulator и currentValue, где x − accumulator, а y − currentValue.

Redux Reducer − это просто функция, которая принимает два параметра. Первый − state, а другой − action.

createStore() отвечает за передачу аргументов редуктору. Немного опустим подробности работы редуктора. Но минимальный код для создания хранилища знать нужно:

import { createStore } from "redux";  
const store = createStore(reducer);

Процесс рефакторинга

Если вы запутались в предыдущем топике, перечитайте его ещё раз или задайте вопрос автору гайда.

Посмотрите на код ниже:

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";

 import { createStore } from "redux";  
 const store = createStore(reducer);  

 class App extends Component {
 render() {
   return <HelloWorld tech={this.state.tech}/>
 }
}

export default App;

Заметили ошибку? В четвертой строке редуктор обращается к функции, которой ещё не существует. Исправим это (редуктор − всего лишь функция) путём создания директории редукторов и файла index.js внутри неё.

Для начала экспортируйте функцию в этот файл:

export default () => {
}

Редуктор всегда что-то возвращает. В примере с Array.reduce() мы вернули сумму и текущее значение.

Для редукторов вы всегда возвращаете newState вашего приложения.

Объясним на примере.

После того как вы придёте в банк и снимите деньги, денег на счету у вас станет меньше и кассир об этом знает.

Поскольку нас пока не волнует обновление состояния, мы будем продолжать возвращаться в newState по мере прохождения state состояния.

Вот так это работает в редукторе:

export default (state) => {
                   return state        
}

Если действие в банке не производится, то ничего не происходит. Так и у нас: если никакой action не был отправлен, то приложение будет всегда возвращаться в позицию state.

Второй аргумент createStore

Когда вы приходите в банк, чтобы снять деньги, вы понимаете, что они там есть, либо когда-то были. Назовём эти деньги начальным депозитом. В Redux тоже есть возможность создания начального депозита с помощью функции initialState:

const store = createStore(reducer, initialState);

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

App.js

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";
import reducer from "./reducers";
import { createStore } from "redux";  

const initialState = { tech: "React " };
const store = createStore(reducer, initialState);

class App extends Component {
 render() {
   return <HelloWorld tech={this.state.tech}/>
 }
 }

export default App;

reducers/index.js

export default state  => {
                   return state        
}

Если вы сейчас попробуете запустить этот код, получите сообщение об ошибке. Так происходит потому, что <App /> больше не связан с объектом состояния. Поскольку теперь store управляет состоянием, state объект нужно убрать из store. Как?

Каждый раз, когда вы создаете хранилище createStore(), созданный store начинает работать с тремя методами. Первый − getState().

В любой момент времени вызов getState метода в созданном store будет возвращать текущее состояние вашего приложения. Вот так:

App.js

import React, { Component } from "react";
import HelloWorld from "./HelloWorld";
import { createStore } from "redux";  

const initialState = { tech: "React " };
const store = createStore(reducer, initialState);  

class App extends Component {
 render() {
   return <HelloWorld tech={store.getState().tech}/>
 }
 }

Reducers/Reducer.js

export default state => {
                   return state        
}

И это все! Вы только что изучили основы Redux и успешно адаптировали простое приложение React для использования Redux.

Приложение React теперь имеет свое состояние, управляемое Redux. Все, что нужно получить от state объекта, будет взято из store.

Надеемся, вы поняли весь процесс рефакторинга. Если что, можете посмотреть материалы Github diffп.

С помощью проекта «Hello World» мы рассмотрели основные концепции и написали Redux-приложение. Несмотря на то, что это крошечный проект, он обеспечивает достойную основу для понимания и развития.

Вот несколько вещей, которые вы должны усвоить:

  • Redux является предсказуемым контейнером состояний для приложений JavaScript.
  • createStore используется для создания Redux store.
  • Редуктор − единственный, кто имеет доступ к функции createStore().
  • Редуктор − это просто функция. Функция, которая принимает два параметра. Первое − state, второе − action.
  • Редуктор всегда возвращает newState.
  • Исходное состояние вашего приложения − initialState − это второй аргумент, который передается в createStore.
  • getState() возвращает текущее состояние вашего приложения.

Интересно было узнать о том, как написать первое Redux-приложение?

Вас также могут заинтересовать другие статьи по теме:

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

eFusion
01 марта 2020

ТОП-15 книг по JavaScript: от новичка до профессионала

В этом посте мы собрали переведённые на русский язык книги по JavaScript – ...
admin
10 июня 2018

Лайфхак: в какой последовательности изучать JavaScript

Огромный инструментарий JS и тонны материалов по нему. С чего начать? Расск...