Изучите Redux - Введение в управление состоянием с помощью React

Это сообщение было впервые опубликовано на CodingTheSmartWay.com.

Подпишитесь на YouTube

Redux - это контейнер с предсказуемым состоянием для JavaScript и поставляется как внешняя библиотека, которую можно использовать вместе с интерфейсными фреймворками, такими как React, Angular, Ember.js, Backbone и т. Д. Однако Redux чаще всего используется вместе с React.

Проблема с традиционной архитектурой MVC

Прежде чем углубляться в концепции Redux, давайте сначала сосредоточимся на проблемах, которые он решает. Паттерн модель-представление-контроллер (MVC) знаком большинству современных интерфейсных веб-разработчиков. Этот шаблон описывает разделение между данными (моделью), представлением (представлением) и логикой приложения (контроллером). Это гарантирует, что ваше приложение построено структурированным образом и вы достигнете разделения задач.

Однако недостатком является то, что вы теряете контроль над потоком данных. Как правило, поток данных двунаправленный. Ввод пользователя в один компонент может повлиять на другие компоненты и наоборот. Управление потоком данных и обеспечение соответствующего обновления всех компонентов пользовательского интерфейса - это задача, подверженная ошибкам.

Как государственное управление решает проблему

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

Строительные блоки Redux

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

Действия

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

Действия - это объекты JavaScript, как вы можете видеть в следующем примере:

{
    type: LOGIN_USER,
    payload: {username: ‘sebastian’, password: ‘123456’}
}

Здесь у объекта действия есть два свойства:

  • type: константа для определения типа действия.
  • полезная нагрузка: объект, назначенный этому свойству, содержит данные, которые отправляются в магазин.

Объекты действия создаются с помощью функций. Эти функции называются создателями действий:

function authUser(data) {
    return {
        type: LOGIN_USER,
        payload: data
    }
}

Здесь вы можете видеть, что единственная цель функции создания действия - вернуть объект действия, как описано.

Вызвать действия в приложении легко с помощью метода dispatch:

dispatch(authUser(data));

Редукторы

Редукторы - это самый важный строительный блок, и важно понимать их концепцию. Редукторы - это чистые функции JavaScript, которые принимают текущее состояние приложения и объект действия и возвращают новое состояние приложения:

function myReducer (state , action)  {
  switch (action.type) {
    case 'LOGIN_USER':
      return Object.assign({}, state, {
        auth: action.payload
      })
    default:
      return state
  }
}

Здесь важно отметить, что состояние не изменяется напрямую. Вместо этого создается новый объект состояния (на основе старого состояния), и выполняется обновление до нового состояния.

Магазин

Хранилище - это центральные объекты, в которых хранится состояние приложения. Хранилище создается с помощью метода createStore из библиотеки Redux:

import { createStore } from ‘redux’;
let store = createStore(myReducer);

Вам необходимо передать функцию редуктора в качестве параметра. Теперь вы готовы передать действие в хранилище, которое обрабатывается редуктором:

let authData = {username: ‘sebastian’, password: ‘123456’};
store.dispatch(authUser(authData));

На следующем рисунке показан поток данных между строительными блоками:

Создание образца приложения React-Redux

Что мы собираемся построить

Теперь давайте продолжим практическую часть этого руководства и начнем создавать реальное приложение React-Redux с нуля. На следующем снимке экрана вы можете увидеть, что мы собираемся построить:

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

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

Приступим!

Настройка проекта React

Сначала нам нужно настроить новый проект React. Самый простой способ сделать это - использовать create-response-app:

$ create-react-app my-redux-app

Войдите во вновь созданный каталог my-redux -app и запустите веб-сервер разработки:

$ cd my-redux-app
$ yarn start

Наконец, нам нужно убедиться, что Redux добавлен в наш проект: < br />
$ yarn add redux

Реализация действий

Давайте начнем реализацию с создания нового файла index.js в папке src / actions. Этот файл содержит функции создания действий, которые необходимы нашему приложению:

export const voteAngular = () => {
  return {
    type: 'VOTE_ANGULAR'
  }
}
export const voteReact = () => {
  return {
    type: 'VOTE_REACT'
  }
}
export const voteVuejs = () => {
  return {
    type: 'VOTE_VUEJS'
  }
}

Пользователь может выбрать один из трех интерфейсных фреймворков: Angular, React, Vue.js. Здесь мы определяем трех соответствующих создателей действий. Объект действия, который создается во всех трех случаях, просто содержит свойство типа с одним из следующих типов:

  • VOTE_ANGULAR
  • VOTE_REACT
  • VOTE_VUEJS

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

Реализация редукторов

Затем давайте реализуем функцию Reducer. Создайте новую папку src / reducers и в этой папке создайте новый файл index.js и вставьте следующий код:

const initialState = {
  angular: 0,
  react: 0,
  vuejs: 0
}
export default (state = initialState, action) => {
  switch (action.type) {
    case 'VOTE_ANGULAR':
      console.log("Your choice is Angular!")
      return Object.assign({}, state, {
        angular: state.angular + 1
      })
    case 'VOTE_REACT':
      console.log("Your choice is React!")
      return Object.assign({}, state, {
        react: state.react + 1
      })
    case 'VOTE_VUEJS':
      console.log("Your choice is Vue.js")
      return Object.assign({}, state, {
        vuejs: state.vuejs + 1
      })
    default:
      return state
  }
}

Сначала мы определяем объект const, который содержит начальное состояние нашего приложения. Объект состояния состоит из трех свойств: angular, react и vuejs. Первоначально значения этих свойств равны 0.

Чтобы установить начальное состояние, нам нужно присвоить объекту initialState состояние первого параметра функции редуктора по умолчанию. ценить.

Функция Reducer содержит оператор switch, который обрабатывает три случая:

  • VOTE_ANGULAR: если в хранилище было отправлено действие типа VOTE_ANGULAR, создается новый объект состояния и увеличивается свойство состояния angular.
  • VOTE_REACT: если в хранилище было отправлено действие типа VOTE_REACT, создается новый объект состояния и увеличивается свойство состояния react.
  • VOTE_VUEJS: если в хранилище было отправлено действие типа VOTE_VUEJS, создается новый объект состояния и увеличивается свойство состояния vuejs.

В каждом случае метод Object.assign используется для создания нового объекта состояния.

Создание магазина

Чтобы завершить Redux-части наших приложений, давайте создадим хранилище в файле index.js:

import { createStore } from 'redux';
import myApp from './reducers';
let store = createStore(myApp);
function render() {
  ReactDOM.render(
    [...]
  );
}
store.subscribe(render);
render();

Сначала убедитесь, что createStore импортирован. Кроме того, мы импортируем Reducer. Затем создается хранилище путем вызова createStore и передачи Reducer в качестве параметра.

Наконец, нам нужно вызвать store.subscribe(render). Это гарантирует, что функция render вызывается всякий раз, когда изменяется состояние приложения.

Реализация компонента приложения

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

import React, { Component } from 'react';
import { voteAngular, voteReact, voteVuejs } from './actions'
import './App.css';
class App extends Component {
  constructor(props) {
    super(props);
    this.store = this.props.store;
  }
  handleVoteAngular = () => {
    this.store.dispatch(voteAngular());
  }
  handleVoteReact = () => {
    this.store.dispatch(voteReact());
  }
  handleVoteVuejs = () => {
    this.store.dispatch(voteVuejs());
  }
  render() {
    return (
      <div>
        <div className="jumbotron" style={{'textAlign': 'center'}}>
          <img src="ctsw_logo.png" height="96" alt="CodingTheSmartWay.com"></img>
          <h2>What is your favorite front-end development framework 2017?</h2>
          <h4>Click on the logos below to vote!</h4>
          <br />
          <div className="row">
            <div className="col-xs-offset-3 col-xs-2">
              <img src="angular_logo.png" height="96" alt="Angular" onClick={this.handleVoteAngular}></img>
            </div>
            <div className="col-xs-2">
              <img src="react_logo.png" height="96" alt="React" onClick={this.handleVoteReact}></img>
            </div>
            <div className="col-xs-2">
              <img src="vuejs_logo.png" height="96" alt="Vue.js" onClick={this.handleVoteVuejs}></img>
            </div>
          </div>
        </div>
      </div>
    );
  }
}
export default App;

Как видите, мы используем некоторые классы CSS Bootstrap в коде JSX. Jumbotron используется в качестве основного элемента, который содержит заголовки и логотипы. Для каждого логотипа мы используем атрибут onClick, чтобы связать метод обработчика события с событием щелчка изображения.

Три метода обработчика событий handleVoteAngular, handleVoteReact и handleVoteVuejs также реализованы. Единственная задача, которую необходимо выполнить в методах обработчика событий, - это отправить соответствующее действие в магазин. Чтобы получить доступ к магазину приложений, объект store передается компоненту как свойство:

function render() {
  ReactDOM.render(
    <div className="container">
      <App store={store}/>
    </div>,
    document.getElementById('root')
  );
}

Внедрить компонент результатов

Отображением результатов голосования занимается другой компонент: Результаты. Давайте создадим новый файл results.js в папке src / components. Исходный код можно увидеть в следующем листинге:

import React, { Component } from 'react';
class Results extends Component {
  constructor(props) {
    super(props);
    this.store = this.props.store;
  }
  votesAngularInPercent() {
    if (this.store.getState().angular) {
      return (this.store.getState().angular / (this.store.getState().angular + this.store.getState().react + this.store.getState().vuejs)) * 100
    } else {
      return 0
    }
  }
  votesReactInPercent() {
    if (this.store.getState().react) {
      return (this.store.getState().react / (this.store.getState().angular + this.store.getState().react + this.store.getState().vuejs)) * 100
    } else {
      return 0
    }
  }
  votesVuejsInPercent() {
    if (this.store.getState().vuejs) {
      return (this.store.getState().vuejs / (this.store.getState().angular + this.store.getState().react + this.store.getState().vuejs)) * 100
    } else {
      return 0
    }
  }
  votesAngularInPercentStyle() {
    return {
      width: this.votesAngularInPercent()+'%'
    }
  }
  votesReactInPercentStyle() {
    return {
      width: this.votesReactInPercent()+'%'
    }
  }
  votesVuejsInPercentStyle() {
    return {
      width: this.votesVuejsInPercent()+'%'
    }
  }
  render() {
    return (
      <div>
        <span className="label label-danger">Angular: {this.votesAngularInPercent().toFixed(2) + '%'}</span>
        <div className="progress progress-striped active">
          <div className="progress-bar progress-bar-danger" style={this.votesAngularInPercentStyle()}></div>
        </div>
        <span className="label label-info">React: {this.votesReactInPercent().toFixed(2) + '%'}</span>
        <div className="progress progress-striped active">
          <div className="progress-bar progress-bar-info" style={this.votesReactInPercentStyle()}></div>
        </div>
        <span className="label label-success">Vue.js: {this.votesVuejsInPercent().toFixed(2) + '%'}</span>
        <div className="progress progress-striped active">
          <div className="progress-bar progress-bar-success" style={this.votesVuejsInPercentStyle()}></div>
        </div>
      </div>
    )
  }
}
export default Results;

Опять же, хранилище передается в компонент как свойство. Конструктор используется для того, чтобы сделать магазин доступным через this.store.

Чтобы выразить текущие результаты голосования в процентах, определены три вспомогательных метода: membersAngularInPercent , VoicesReactInPercent и VoicesVuejsInPercent.

Три дополнительных вспомогательных метода (VoicesAngularInPercentStyle, voteReactInPercentStyle и voteVuejsInPercentStyle) определены для возврата значения ширины CSS для текущего результата голосования. Это используется в коде JSX для установки значения стиля элемента индикатора выполнения.

При включении вывода компонента Results в наше приложение вносятся следующие изменения в index.js:

[...]
import Results from './components/results';

let store = createStore(myApp);
function render() {
  ReactDOM.render(
    <div className="container">
      <App store={store}/>
      <hr />
      <Results store={store}/>
    </div>,
    document.getElementById('root')
  );
}
store.subscribe(render);
render();

Сначала добавляются операторы импорта для Results, а затем элемент <Results store={store}/> добавляется в код JSX внутри функции рендеринга.

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

Заключение

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

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

Хотите глубже погрузиться в React - ознакомьтесь с отличным онлайн-курсом:

Полный курс для разработчиков веб-приложений на React

Это сообщение было впервые опубликовано на CodingTheSmartWay.com.