Изучение того, что и почему Redux

1656673939 izuchenie togo chto i pochemu

Питер Мбануго

F6OdxzJFbBYWL8YTaMzMtylfqC7o03b4MQg7

Что такое Redux и зачем он мне нужен?

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

Это достигается путем отделения различных данных, представляющих состояние программы от представления этих данных.

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

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

Проблема

Состояние приложения можно хранить в случайных объектах в памяти. Можно также сохранить определенное состояние в DOM.

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

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

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

Цель

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

Решение: однонаправленный поток данных и единственный источник правды

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

В Facebook они хотели более простой способ предусмотреть изменения состояния, и поэтому придумали шаблон под названием Флюс. Flux – это шаблон уровня данных для управления потоком данных. Он предполагает, что данные должны поступать только в одном направлении, состояние программы содержится в одном месте – источник правды – и логика для изменения состояния только в одном месте.

Флюс

s4L26593yAq7tsGDmN8h03mSiso2epkS78dY

На диаграмме выше описан поток данных:

  • Данные поступают из сохранять — источник правды — в просмотреть. Представление считывает данные и представляет их пользователю, пользователь взаимодействует с разными компонентами представления, и если ему нужно изменить состояние программы, он выражает свое намерение сделать это через действие.
  • Действие фиксирует способы, которыми что-либо может взаимодействовать с вашей программой. Это обычный объект с полем «тип» и некоторыми данными. The диспетчер отвечает за передачу действия в магазин. Он не содержит логики изменения состояния, скорее, сам магазин делает это внутренне.
  • Вы можете иметь несколько хранилищ, каждое из которых содержит данные другого домена программы. Магазин реагирует на действия, соответствующие состоянию, которое он поддерживает. Если он обновляет состояние, он также уведомляет о просмотрах, подключенных к этому хранилищу, излучая событие.
  • Представление получает уведомление и получает данные из хранилища, а затем повторно отображает. Когда состояние нужно обновить снова, оно проходит тот же цикл, что позволяет легко обдумать вашу заявку и сделать изменения состояния предсказуемыми.

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

Redux
Есть разные реализации этого шаблона. У нас есть Fluxxor, Flummox, Reflux и т.д. Но Redux стоит выше их всех.

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

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

Redux, следуя стопам Flux, имеет три концепции:

  • Единственный источник правды: Я упоминал о необходимости этого. Redux имеет то, что он называет сохранять. Хранилище — это объект, содержащий все состояние программы. Различные части состояния хранятся в дереве объектов. Это облегчает реализацию отмены/повтора.

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

{        "cartItem" : [            {                "productName" : "laser",                "quantity" : 2            },            {                "productName" : "shirt",                "quantity" : 2            }        ],        "selectedProduct" : {            "productName" : "Smiggle",            "description" : "Lorem ipsum ... ",            "price" : "$30.04"        }    }
  • Состояние доступно только для чтения: состояние не может изменяться непосредственно представлением или каким-либо другим процессом (возможно, в результате обратного вызова сети или другого события).
    Чтобы изменить состояние, вы должны выразить свое намерение, выполнив действие. Действие – это обычный объект, описывающий ваше намерение, и содержащий свойство типа и некоторые другие данные. Действия можно регистрировать и воспроизводить позже, что делает его полезным для отладки и тестирования.

На примере нашей корзины для покупок мы можем запустить следующее действие:

store.dispatch({      type: 'New_CART_ITEM',      payload: {                   "productName" : "Samsung S4",                   "quantity" : 2                }    })    dispatch(action) emits the action, and is the only way to trigger a state change. To retrieve the state tree, you call store.getState().
  • Редуктор: редукторы отвечают за определение того, какие изменения состояния произойдут, а затем трансформируют их, чтобы отобразить новые изменения.
    Reducer – это чистая функция, которая принимает предыдущие (текущее состояние, которое собирается изменить) и действие, определяет, как обновить состояние на основе типа действия, преобразует его и возвращает следующее состояние (обновленное состояние).

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

Для корзины для покупок будет добавлен новый продукт в корзину:

function shoppingCart(state = [], action) {      switch (action.type) {        case 'New_CART_ITEM':          return [...state, action.payload]        default:          return state      }    }

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

Есть вещи, которые вы никогда не должны делать внутри редуктора, и они:

  • Измените его доводы.
  • Следуйте побочным эффектам, таким как вызовы API и переходы маршрутизации.
  • Вызов нечистых функций.

Практический пример

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

Чтобы настроить, выполните следующие команды в терминале:

$ git clone git@github.com:StephenGrider/ReduxSimpleStarter.git    $ cd ReduxSimpleStarter    $ npm install

Мы только что клонировали первоначальный шаблон для того, что мы создаем в этом разделе. Мы настроили React и скачали пакеты Redux и react-redux npm. Мы создадим приложение, которое позволит нам делать краткие заметки как элементы дел или ключевые слова, напоминающие нам о чем-то.

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

Создайте новый файл под названием types.js в ./src/actions с таким содержанием:

export const FETCH = 'FETCH';    export const CREATE = 'CREATE';    export const DELETE = 'DELETE';

Далее нам нужно определить действия и отправить их по необходимости. Создатели действий – это функции, которые помогают создавать действия, и результат передается dispatch().

Редактировать index.js файл в папке действий с таким содержимым:

import { FETCH, DELETE, CREATE } from './types';
    export function fetchItems() {      return {        type: FETCH      }    }
    export function createItem(item) {      let itemtoAdd = {        [Math.floor(Math.random() * 20)]: item      };
      return {        type: CREATE,        payload: itemtoAdd      }    }
    export function deleteItem(key) {      return {        type: DELETE,        payload: key      }    }

Мы определили три действия по созданию, удалению и получению элементов из магазина. Дальше нам нужно создать редуктор. Math.floor(Math.random() * 20 используется для назначения уникального ключа новому прилагаемому элементу. Это не оптимально, но мы используем его здесь только для этой демонстрации.

Добавьте новый файл в каталог редуктора под названием item-reducer.js:

import _ from 'lodash';    import { FETCH, DELETE, CREATE } from '../actions/types';
    export default function(state = {}, action) {      switch (action.type) {        case FETCH:          return state;        case CREATE:          return { ...state, ...action.payload };        case DELETE:          return _.omit(state, action.payload);      }
      return state;    }

Определив редуктор, нам нужно подключить его к нашей программе с помощью combineReducer()функция.

Откройте и отредактируйте файл внутри папки редуктора. index.js:

import { combineReducers } from 'redux';    import ItemReducer from './item-reducer';
    const rootReducer = combineReducers({      items: ItemReducer    });
    export default rootReducer;

Мы передаем созданный нами редуктор к combinedReducer функция, где ключ – это часть состояния, за которую отвечает редуктор.

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

С combineReducer мы рассказываем Redux, как создать состояние нашей программы. Итак, думать и проектировать, как смоделировать состояние программы в Redux – это то, что вы должны сделать заранее.

После настройки Redux относительно того, как управлять нашим состоянием, следующее – подключить View (которым управляет React) к Redux.

Создайте новый файл item.js внутри компоненты каталог. Это будет разумный компонент, поскольку он знает, как взаимодействовать с Redux для чтения состояния и запроса изменения состояния.

Добавьте содержимое ниже к этому файлу:

import React, { Component } from 'react';    import { connect } from 'react-redux';    import * as actions from '../actions';
    class Item extends Component {      handleClick() {        this.props.deleteItem(this.props.id);      }
      render() {        return (          <li className="list-group-item">            {this.props.item}            <button              onClick={this.handleClick.bind(this)}              className="btn btn-danger right">              Delete            </button>          </li>        );      }    }
    export default connect(null, actions)(Item);

Этот компонент отображает элемент и позволяет его удалить. The connect() функция принимает компонент React в его безмолвном состоянии (она не знает ни о Redux, ни о том, как с ним взаимодействовать) и создает разумный компонент. Он соединяет создатели действий с компонентом таким образом, что если вызывается создатель действия, возвращенное действие посылается редукторам.

Мы также создадим второй разумный компонент, который будет отображать предыдущий компонент как список элементов, а также позволит нам добавлять новые элементы.

Обновите файл app.js в папке компонентов с приведенным ниже содержимым:

import _ from 'lodash';    import React, { Component } from 'react';    import { connect } from 'react-redux';    import * as actions from '../actions';    import Item from './item';
    class App extends Component {      state = { item: '' };
      componentWillMount() {        this.props.fetchItems();      }
      handleInputChange(event) {        this.setState({ item: event.target.value });      }
      handleFormSubmit(event) {        event.preventDefault();
        this.props.createItem(this.state.item, Math.floor(Math.random() * 20))      }
      renderItems() {        return _.map(this.props.items, (item, key) => {          return <Item key={key} item={item} id={key} />        });      }
      render() {        return (          <div>            <h4>Add Item</h4>            <form onSubmit={this.handleFormSubmit.bind(this)} className="form-inline">              <div className="form-group">                <input                  className="form-control"                  placeholder="Add Item"                  value={this.state.item}                  onChange={this.handleInputChange.bind(this)} />                <button action="submit" className="btn btn-primary">Add</button>              </div>            </form>            <ul className="list-group">              {this.renderItems()}            </ul>          </div>        );      }    }
    function mapStateToProps(state) {      return { items: state.items };    }
    export default connect(mapStateToProps, actions)(App)

Это разумный компонент (или контейнер), вызывающий fetchItems() создатель действий после загрузки компонента. Мы также использовали connect функция для связи состояния приложения в Redux с нашим компонентом React. Это достигается с помощью функции mapStateToProps который принимает объект дерева состояния Redux в качестве входного параметра и отображает его часть (элементы) на свойства компонента React. Это позволяет нам получить к нему доступ с помощью this.props.items. Остаток файла позволяет нам принимать пользовательские данные и добавлять их в состояние программы.

Запустите приложение с помощью npm start и попробуйте добавить несколько элементов, как на изображении ниже:

lM02K5jSbKEBTdtx11XowQmN5NNqxdCnm5e5

Резюме

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

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

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

Он руководствуется тремя основными принципами, а именно:

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

Это делает его контейнером предполагаемого состояния для JavaScript.

Дальнейшее чтение

Исходный код

Найдите исходный код здесь.

Это было первоначально опубликовано на Pusher.

Добавить комментарий

Ваш адрес email не будет опубликован.