Простой и элегантный способ реализовать однонаправленный поток данных

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

Redux - обычно встречающийся с React, но его можно использовать отдельно - оказался успешной реализацией Flux. Он может обрабатывать состояние приложения и очень эффективно связывать его с пользовательским интерфейсом. RefluxJS, еще одна независимая реализация, переработала Flux, сделав его более динамичным и более дружественным к функциональному реактивному программированию (FRP).

Вдохновленный этими реализациями, я написал библиотеку StateX, которая поможет вам реализовать архитектуру однонаправленного потока данных для angular 2 (или выше). В этой статье мы увидим, как использовать эту библиотеку для реализации архитектуры Flux за 5 простых шагов.

Установить

npm install statex --save

5 простых шагов

1. Определить состояние

Чтобы получить максимальную отдачу от TypeScript, объявите интерфейс, определяющий структуру состояния приложения. Как и в Redux, состояние приложения (или просто State) - это объект, который будет содержать все данные вашего приложения. Этот шаг не является обязательным; вы могли бы просто обойтись any, но я настоятельно рекомендую вам определить состояние приложения.

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

2. Определите действие

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

Итак, здесь действия определяются как классы с необходимыми аргументами, передаваемыми конструктору. Таким образом, мы выиграем от проверки типов; никогда больше мы не пропустим действие, пропустим обязательный параметр или передадим неправильный параметр.

Не забудьте расширить действие из класса Action, который предоставляет statex. Это делает ваше действие доступным для прослушивания и диспетчеризации.

3. Создать действие "Сохранить и привязать"

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

Второй параметр функции редуктора (addTodo) - это действие (типа AddTodoAction); action использует эту информацию для привязки правильного действия. Также не забудьте расширить этот класс от Store.

Вы заметили @Injectable()? Что ж, магазины представляют собой вводимые модули и для создания экземпляров используют инъекцию зависимостей Angular. Так что позаботьтесь о добавлении магазина в список providers и добавлении его в app.component. Прочтите раздел Организация магазинов, чтобы узнать больше.

4. Действие по отправке

Нет диспетчера синглтонов! Вместо этого эта библиотека позволяет каждому действию действовать как диспетчер. На одну зависимость меньше: определять, внедрять и поддерживать.

Каждое действие, которое определяет statex, доступно для диспетчеризации и прослушивания.

5. Потребляйте данные

Потребление данных - важная часть. Мы не хотим, чтобы пользовательский интерфейс обрабатывал данные, если они не изменились. @data декоратор использует функцию селектора для выбора подмножества состояния приложения для работы. Свойство обновляется только тогда, когда значение, возвращаемое функцией селектора, изменяется с предыдущего состояния на текущее. Кроме того, как и функция map, вы можете сопоставить данные с другим значением по вашему выбору. Он совместим с проверкой типов TypeScript.

Используйте декоратор @data и функцию-селектор (параметр декоратора) для получения обновлений из состояния приложения.

В этом случае todos обновляется при изменении state.todos. Жизнь не всегда так проста! Нам может потребоваться получить дополнительные свойства из данных, иногда используя сложные вычисления.

Следовательно, @data также можно использовать с функциями.

Неизменяемое состояние приложения

Чтобы воспользоваться преимуществами стратегии обнаружения изменений Angular 2 - OnPush -, нам нужно убедиться, что состояние действительно неизменяемо.

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

- Immutable.js

Эта библиотека использует seamless-immutable - неизменяемые JS структуры данных, которые обратно совместимы с обычными массивами и объектами - для сохранения неизменности состояния приложения ( сравнение между seamless-immutable и знаменитой библиотекой Immutable.js Facebook находится здесь).

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

Поэтому функция-редуктор должна либо возвращать часть состояния, которое требует изменения (рекомендуется), либо вместо этого новое состояние приложения, заключенное в ReplaceableState.

Организация магазинов

Создайте массив STORES и класс Stores (снова @Injectable) для обслуживания магазинов. При создании нового магазина не забудьте:

  1. Добавить магазин в массив STORES
  2. Внести в конструктор Store

При настройке проекта добавьте STORES в массив providers в app.module.ts

И, наконец, вставьте Stores в корневой компонент (app.component.ts)

Обеспечение совместимости вашего кода с AOT

Если вы использовали версию до v1.0.0, этот раздел поможет вам провести рефакторинг кода, чтобы сделать его совместимым с AOT. Функция-селектор для @data декоратора должна быть экспортированной отдельной функцией, чтобы избежать ошибки AOT ниже:

ERROR in Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function

Поэтому проведите рефакторинг вашего кода с:

@Component({
  ...
})
export class TodoComponent {
  
  @data((state: State) => state.todos)
  todos: Todo[];
}

to

export function selectTodos(state: State) {
  return state.todos;
}
@Component({
  ...
}
export class TodoComponent extends DataObserver {
  
  @data(selectTodos)
  todos: Todo[];
}

Не забудьте расширить свой класс с DataObserver. Очень важно указать компилятору Angular сохранять ngOnInit и ngOnDestroy события жизненного цикла, что может быть достигнуто только путем реализации интерфейсов OnInit иOnDestroy. Из-за этого ограничения все компоненты using@data должны расширяться от DataObserver, который устанавливает ngOnInit и ngOnDestroy правильно; @data, в свою очередь, зависит от этих функций. Однако, если вы хотите расширить свой класс из собственного базового класса, вы можете сделать это, убедившись, что ngOnInit иngOnDestroy реализованы правильно.

Образец кода

Пример кода здесь:

https://github.com/rintoj/statex/tree/master/examples/todo-ng-ts

Эта библиотека также доступна для React. Пожалуйста, проверьте



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