Оглавление

1. Фон

2. Стратегии государственного управления: какие варианты?

- через взаимодействие компонента через привязки ввода и генераторы выходных событий;

- Через сервисы Angular с использованием простых переменных и обещаний;

- Через службы наблюдаемых данных - службы Angular и RxJS;

- Через Redux Pattern и RxJS.

3. Заключение

Фон

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

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

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

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

Стратегии государственного управления: какие варианты?

Прежде всего, поскольку Angular - это комплексная платформа / платформа JS, которая содержит все необходимое для решения всех видов задач разработки, она предоставляет нам две встроенные функции состояния, а именно:

1. Взаимодействие с иерархическими компонентами, обычно с использованием компонентов с отслеживанием состояния и без состояния через привязки @Input и настраиваемые события @Output.

2. Через сервисы Angular с использованием простых переменных для временного сохранения данных и Promises как наиболее распространенный тип системы Push в JavaScript сегодня.

Во-вторых, есть еще два варианта, более сложные, но в то же время более мощные и эффективные:

3. Через сервисы наблюдаемых данных - сервисы Angular с библиотекой RxJS, которая является реактивным расширением JavaScript «b̶e̶a̶s̶t̶».

4. Через шаблон Redux с библиотекой RxJS.

Давайте углубимся в каждую по отдельности.

Взаимодействие иерархического компонента

Основная концепция заключается в наличии родительского компонента с «сохранением состояния», который делегируется дочерним компонентам «без состояния». Такая структура имеет несколько важных особенностей: она ясна и предсказуема, ее легко протестировать, и нетрудно увидеть, что отразится на внесении изменений. Когда данные изменяются в родительском компоненте, легко найти подчиненные дочерние компоненты, которые могут быть затронуты (см. Рисунок 1).

Однако такой подход удобен только в очень простом приложении, и как только архитектура вашего приложения становится более сложной или вам просто нужно обмениваться данными между отдельными модулями / компонентами через службы Angular, это становится бесполезным и болезненным (см. Рисунок 2).

Угловые сервисы, переменные и обещания

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

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

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

Обратите внимание на основные недостатки Promises:

- вы не можете запускать Promise только тогда, когда он вам нужен, потому что он выполняется сразу и только один раз - при создании;

- Обещания возвращают только одно значение или сообщение об ошибке;

- запрос, инициированный из обещания, не может быть отменен, например HTTP-запрос, который выполняет поиск по событию нажатия клавиши, будет выполняться столько раз, сколько мы нажимаем клавишу;

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

Следовательно, обещаниями определенно сложно управлять в больших приложениях, более того, вы теряете огромную функциональность по сравнению с шаблоном Observable.

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

Observable Data Services - сервисы Angular с RxJS

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

Наблюдаемые сервисы данных - потоки данных, которые обеспечивают большую гибкость в разработке приложения и управлении состоянием приложения с помощью нескольких сервисов Angular (синглтонов).

На мой взгляд, паттерн Observable store - отличное решение для простых приложений вместо громоздкого стороннего хранилища библиотек. Однако RxJS не решает никаких проблем, и у него определенно есть проблемы. Основная проблема заключается в том, что нет централизованной, я имею в виду единой и утвержденной системы, как правильно реализовать настраиваемое управление состоянием для приложения.

Давайте рассмотрим пример из моего прошлого опыта (написанный на Angular 5 и версии RxJS 5.5.11) - это простое приложение, которое имеет три основных маршрута:

1. Клиенты - пользователь может видеть список клиентов и его детали.

2. Продукты - пользователь может видеть список продуктов и его детали.

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

Этот подход основан только на чистых потоках RxJS. Рассмотрим это подробнее. Во-первых, мы просто получаем данные из API в потоке invoicesList $, а затем передаем его значение (набор счетов) в поток invoicesListCombined $, который также включает поток customersList $ (набор клиентов), объявленный в службе клиентов. Текущий поток просто преобразует каждый счет, добавляя в него информацию о клиенте.

После этого, чтобы реализовать функциональность в счетах в соответствии со спецификациями и иметь возможность расширять его в будущем, я создал поток invoicesCollection $ base, который имеет асинхронную подписку в шаблоне для отображения всего списка счетов-фактур для Пользователь. Затем, очевидно, я расширил его двумя другими потоками: addInvoiceToCollection $ и deleteInvoiceFromCollection $, которые соответственно преобразуют данные основного потока invoicesCollection $.

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

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

Мне потребовалось некоторое время, чтобы найти масштабируемое и многократно используемое индивидуальное решение, которое в конечном итоге оказалось не того стоит, но обо всем по порядку. Чтобы следовать принципу DRY и сделать нашу реализацию State многоразовой во всем приложении Angular, первое, что пришло в голову, - это создать класс с универсальным типом (например, класс StateManagement). Затем я добавил к этому классу простую коллекцию $ stream, которая будет представлять каждую из наших будущих коллекций данных. Итак, каждый раз, вызывая класс StateManagement с помощью оператора «new», я получал новый экземпляр этого класса с новыми данными внутри коллекции $ stream.

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

Более того, я хотел бы обозначить две основные проблемы, которые возникли в рамках этого подхода позже, а именно:

Объединение разных отдельных состояний - мне не понадобится много времени, чтобы испортить множество «CombineLatest» во всем приложении, что не менее чем полностью снижает читабельность и усложняет код. А как насчет других разработчиков, которые могут потратить много времени, чтобы докопаться до сути?

Последовательные запросы и ошибки обнаружения / обработки - например, мне нужно было сначала получить состояние пользователей, а затем на основе текущего идентификатора пользователя получить состояние для определенных счетов-фактур. Поскольку класс «StateManagement» унифицирован, становится проблемой найти подходящее место для выполнения этого конкретного запроса и отловить / обработать его ошибки. Что, если мое приложение будет масштабироваться за счет новых функций, и мне нужно будет добавить больше отдельных последовательных запросов? Да, именно так, целостность кода будет полностью потеряна.

В заключение, если вы хотите реализовать мощное решение для управления состоянием и использовать встроенные функции фреймворка Angular - подход Observable Data Services определенно сработает!

Однако хочу отметить, что:

1. Вы должны хорошо разбираться в библиотеке RxJS и самом Observable Pattern. Вы должны знать, что такое «холодное», «теплое» и «горячее» наблюдаемое, в чем разница между ними, как преобразовывать одно в другое, что и когда использовать.

2. Выявить и обработать ошибки довольно сложно, более того, очень сложно протестировать приложение, если оно работает в соответствии со спецификациями.

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

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

Шаблон Redux с RxJS

Redux, ссылаясь на документацию, является «контейнером с предсказуемым состоянием для приложений JavaScript». Концепция Redux удивительно проста и элегантна, потому что данные перемещаются только в одном направлении, поток данных явный и предсказуемый, поскольку он всегда поступает из одного источника.

Прежде чем мы начнем с примера, рассмотрим несколько определений:

- Компонент - просмотр шаблона, с которым пользователь может взаимодействовать;

- Действие - определяет (отправляет) изменение состояния, которое должно быть выполнено;

- Reducer - чистая функция, то есть не производит побочных эффектов, имеет доступ к текущему состоянию;

- Селектор - определяет, какие именно данные получить из Магазина;

- Эффект - обрабатывает все, что асинхронно или вне приложения.

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

Давайте рассмотрим простой пример для подробного описания потока данных Redux. Представим, что пользователь нажимает кнопку в шаблоне представления компонента, а затем соответствующее действие запускается (отправляется) в Store. Когда действие запускается, редуктор принимает текущее состояние, а данные из действия затем возвращают из него новое состояние.
Редукторы не сохраняют и не изменяют состояние - они просто принимают предыдущее состояние и действие и вернуть новое состояние.

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

Кроме того, в Redux у нас есть селекторы, которые вызываются с помощью метода select Store. Взгляните на рисунок 4.

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

Второе преимущество состоит в том, что селекторы решают одну из основных перечисленных выше проблем подхода Observable Data Services (управление настраиваемыми состояниями) - объединение разных отдельных состояний. Селектор также может передавать и группировать различные фрагменты состояния для создания данных, которые нужны конкретному компоненту.

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

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

Тестирование нашей логики трансформируется в тестирование действий, селекторов и, конечно же, редукторов, которые сами по себе являются чистыми функциями и позволяют нам тестировать сложные пользовательские интерфейсы, утверждая, что функции возвращают определенные данные. Кроме того, вы можете найти отличное расширение npm или браузера для процесса отладки - Redux DevTools, которое может сэкономить вам много времени. Это позволяет вам проверять весь ваш рабочий процесс - вы можете наблюдать за каждой полезной нагрузкой State и Action, кроме того, если Reducer выдает ошибку, вы можете видеть, во время какого именно Action это произошло. Разве не потрясающе?

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

Заключение

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

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

Если вам понравилась эта статья и вы посетите наш сайт 2muchcoffee.com, хлопните в ладоши.