Глубокое погружение в преимущества и особенности NgRx
Опубликовано: 2022-03-11Если тимлид инструктирует разработчика написать много стандартного кода вместо написания нескольких методов для решения определенной проблемы, ему нужны убедительные аргументы. Инженеры-программисты умеют решать проблемы; они предпочитают автоматизировать вещи и избегать ненужных шаблонов.
Несмотря на то, что NgRx поставляется с некоторым шаблонным кодом, он также предоставляет мощные инструменты для разработки. Эта статья демонстрирует, что, потратив немного больше времени на написание кода, вы получите преимущества, оправдывающие затраченные усилия.
Большинство разработчиков начали использовать управление состоянием, когда Дэн Абрамов выпустил библиотеку Redux. Некоторые начали использовать управление состоянием, потому что это было тенденцией, а не потому, что у них его не было. Разработчики, использующие стандартный проект «Hello World» для управления состоянием, могут быстро начать писать один и тот же код снова и снова, безрезультатно увеличивая сложность.
В конце концов, некоторые разочаровались и полностью отказались от государственного управления.
Моя первоначальная проблема с NgRx
Я думаю, что эта стандартная проблема была серьезной проблемой с NgRx. Сначала мы не могли увидеть общую картину, стоящую за этим. NgRx — это библиотека, а не парадигма программирования или мышление. Однако, чтобы полностью понять функциональность и удобство использования этой библиотеки, нам нужно немного расширить наши знания и сосредоточиться на функциональном программировании. Вот когда вы можете писать шаблонный код и радоваться этому. (Я серьезно.) Когда-то я был скептиком NgRx; теперь я поклонник NgRx.
Некоторое время назад я начал использовать управление состоянием. Я прошел через шаблонный опыт, описанный выше, поэтому решил прекратить использование библиотеки. Поскольку я люблю JavaScript, я стараюсь получить хотя бы базовые знания обо всех популярных сегодня фреймворках. Вот что я узнал, используя React.
В React есть функция под названием Hooks. Подобно компонентам в Angular, хуки — это простые функции, которые принимают аргументы и возвращают значения. Хук может иметь в себе состояние, которое называется побочным эффектом. Так, например, простую кнопку в Angular можно перевести в React так:
@Component({ selector: 'simple-button', template: ` <button>Hello {{ name }}</button> `, }) export class SimpleButtonComponent { @Input() name!: string; } export default function SimpleButton(props: { name: string }) { return <button>{props.name} </button>; }Как видите, это простое преобразование:
- SimpleButtonComponent => Простая кнопка
- @Input() имя => props.name
- шаблон => возвращаемое значение
Наша функция SimpleButton в React имеет одну важную характеристику в мире функционального программирования: это чистая функция . Если вы читаете это, я полагаю, вы хотя бы раз слышали этот термин. NgRx.io дважды цитирует чистые функции в ключевых понятиях:
- Изменения состояния обрабатываются чистыми функциями , называемыми
reducers, которые принимают текущее состояние и последнее действие для вычисления нового состояния. - Селекторы — это чистые функции , используемые для выбора, получения и составления частей состояния.
В React разработчикам рекомендуется максимально использовать хуки как чистые функции. Angular также поощряет разработчиков реализовывать тот же шаблон, используя парадигму Smart-Dumb Component.
Именно тогда я понял, что мне не хватает некоторых важных навыков функционального программирования. Понимание NgRx не заняло много времени, так как после изучения ключевых концепций функционального программирования у меня было «Ага! момент»: я улучшил свое понимание NgRx и хотел использовать его больше, чтобы лучше понять преимущества, которые он предлагает.
В этой статье я делюсь своим опытом обучения и знаниями, которые я получил о NgRx и функциональном программировании. Я не объясняю API для NgRx или как вызывать действия или использовать селекторы. Вместо этого я делюсь тем, почему я пришел к выводу, что NgRx — отличная библиотека: это не просто относительно новая тенденция, она предоставляет множество преимуществ.
Начнем с функционального программирования .
Функциональное программирование
Функциональное программирование — это парадигма, которая сильно отличается от других парадигм. Это очень сложная тема с множеством определений и рекомендаций. Однако функциональное программирование содержит некоторые основные концепции, и их знание является необходимым условием для освоения NgRx (и JavaScript в целом).
Вот эти основные понятия:
- Чистая функция
- Неизменяемое состояние
- Побочный эффект
Повторяю: это просто парадигма, не более того. Не существует библиотеки functions.js, которую мы загружаем и используем для написания функционального программного обеспечения. Это просто способ думать о написании приложений. Начнем с самой важной базовой концепции: чистой функции .
Чистая функция
Функция считается чистой функцией, если она следует двум простым правилам:
- Передача одних и тех же аргументов всегда возвращает одно и то же значение
- Отсутствие наблюдаемого побочного эффекта внутри выполнения функции (изменение внешнего состояния, вызов операции ввода-вывода и т. д.)
Таким образом, чистая функция — это просто прозрачная функция, которая принимает некоторые аргументы (или вообще не принимает аргументы) и возвращает ожидаемое значение. Вы уверены, что вызов этой функции не приведет к побочным эффектам, таким как подключение к сети или изменение некоторого глобального состояния пользователя.
Давайте рассмотрим три простых примера:
//Pure function function add(a,b){ return a + b; } //Impure function breaking rule 1 function random(){ return Math.random(); } //Impure function breaking rule 2 function sayHello(name){ console.log("Hello " + name); }- Первая функция чистая, потому что она всегда будет возвращать один и тот же ответ при передаче одних и тех же аргументов.
- Вторая функция не является чистой, поскольку она недетерминирована и возвращает разные ответы при каждом вызове.
- Третья функция не является чистой, поскольку использует побочный эффект (вызов
console.log).
Легко определить, чистая функция или нет. Почему чистая функция лучше нечистой? Потому что так проще думать. Представьте, что вы читаете исходный код и видите вызов функции, который, как вы знаете, является чистым. Если имя функции правильное, вам не нужно его исследовать; вы знаете, что это ничего не меняет, оно возвращает то, что вы ожидаете. Это очень важно для отладки, когда у вас есть огромное корпоративное приложение с большим количеством бизнес-логики, так как это может значительно сэкономить время.
Кроме того, это просто проверить. Вам не нужно ничего вводить или имитировать некоторые функции внутри него, вы просто передаете аргументы и проверяете, соответствует ли результат. Существует тесная связь между тестом и логикой: если компонент легко протестировать, то легко понять, как и почему он работает.
Чистые функции поставляются с очень удобной и удобной для производительности функциональностью, называемой мемоизацией. Если мы знаем, что вызов одних и тех же аргументов вернет одно и то же значение, мы можем просто кэшировать результаты и не тратить время на повторный вызов. NgRx определенно находится на вершине мемоизации; это одна из основных причин, почему это быстро.
Вы можете спросить себя: «А как насчет побочных эффектов? Куда они идут?" В своей речи GOTO Расс Олсен шутит, что наши клиенты платят нам не за чистые функции, а за побочные эффекты. Это правда: никому нет дела до чистой функции Калькулятора, если она нигде не печатается. Побочные эффекты занимают свое место во вселенной функционального программирования. Мы скоро это увидим.
А пока давайте перейдем к следующему шагу в поддержке сложных архитектур приложений, следующей основной концепции: неизменяемое состояние .
Неизменяемое состояние
Существует простое определение неизменяемого состояния:
- Вы можете только создать или удалить состояние. Вы не можете обновить его.
Проще говоря, чтобы обновить возраст объекта User … :
let user = { username:"admin", age:28 }… вы должны написать это так:
// Not like this newUser.age = 30; // But like this let newUser = {...user, age:29 }Каждое изменение — это новый объект, свойства которого скопированы из старых. Таким образом, мы уже находимся в форме неизменного состояния.
Строка, логическое значение и число являются неизменяемыми состояниями: вы не можете добавлять или изменять существующие значения. Напротив, Date является изменяемым объектом: вы всегда манипулируете одним и тем же объектом даты.
Неизменяемость применяется во всем приложении: если вы передаете пользовательский объект внутри функции, которая изменяет свой возраст, она не должна изменять пользовательский объект, она должна создать новый пользовательский объект с обновленным возрастом и вернуть его:
function updateAge(user, age) { return {...user, age: age) } let user = {username: 'admin', age: 29}; let newUser = updateAge(user, 32);Почему мы должны уделять этому время и внимание? Есть несколько преимуществ, которые стоит подчеркнуть.
Одним из преимуществ серверных языков программирования является параллельная обработка. Если изменение состояния не зависит от ссылки и каждое обновление представляет собой новый объект, вы можете разделить процесс на части и работать над одной и той же задачей с бесчисленным количеством потоков, не разделяя одну и ту же память. Вы даже можете распараллелить задачи между серверами.
Для таких фреймворков, как Angular и React, параллельная обработка — один из наиболее выгодных способов повышения производительности приложения. Например, Angular должен проверять свойства каждого объекта, которые вы передаете через привязки ввода, чтобы определить, должен ли компонент быть перерисован или нет. Но если мы установим ChangeDetectionStrategy.OnPush вместо дефолтного, он будет проверять по ссылке, а не по каждому свойству. В большом приложении это определенно экономит время. Если мы постоянно обновляем наше состояние, мы получаем прирост производительности бесплатно.
Другое преимущество неизменяемого состояния, характерное для всех языков программирования и сред, похоже на преимущества чистых функций: о нем легче думать и тестировать. Когда изменение — это новое состояние, рожденное из старого, вы точно знаете, над чем работаете, и можете точно отследить, как и где изменилось состояние. Вы не теряете историю обновлений и можете отменить/повторить изменения состояния (например, React DevTools).
Однако, если одно состояние будет обновлено, вы не будете знать историю этих изменений. Думайте о неизменном состоянии, как история транзакций для банковского счета. Это практически обязательно.
Теперь, когда мы рассмотрели неизменяемость и чистоту, давайте займемся оставшейся основной концепцией: побочным эффектом .
Побочный эффект
Мы можем обобщить определение побочного эффекта:
- В информатике говорят, что операция, функция или выражение имеют побочный эффект, если они изменяют некоторые значения переменных состояния за пределами своего локального окружения. Другими словами, он имеет наблюдаемый эффект, помимо возврата значения (основной эффект) инициатору операции.
Проще говоря, все, что изменяет состояние вне области действия функции — все операции ввода-вывода и некоторая работа, не связанная напрямую с функцией, — можно считать побочным эффектом. Однако мы должны избегать использования побочных эффектов внутри чистых функций, потому что побочные эффекты противоречат философии функционального программирования. Если вы используете операцию ввода/вывода внутри чистой функции, то она перестает быть чистой функцией.
Тем не менее, нам нужно где-то иметь побочные эффекты, так как приложение без них было бы бессмысленным. В Angular не только чистые функции должны быть защищены от побочных эффектов, мы также должны избегать их использования в компонентах и директивах.
Давайте посмотрим, как мы можем реализовать красоту этой техники внутри фреймворка Angular.
Функциональное угловое программирование
Одна из первых вещей, которые нужно понять об Angular, — это необходимость как можно чаще разделять компоненты на более мелкие компоненты, чтобы облегчить обслуживание и тестирование. Это необходимо, так как нам нужно разделить нашу бизнес-логику. Кроме того, разработчикам Angular рекомендуется оставлять компоненты только для целей рендеринга и перемещать всю бизнес-логику внутрь сервисов.
Чтобы расширить эти концепции, пользователи Angular добавили в свой словарь шаблон «Тупой-умный компонент». Этот шаблон требует, чтобы вызовы службы не существовали внутри небольших компонентов. Поскольку бизнес-логика находится в службах, нам по-прежнему нужно вызывать эти методы службы, ждать их ответа и только после этого вносить какие-либо изменения состояния. Итак, внутри компонентов есть некоторая поведенческая логика.
Чтобы избежать этого, мы можем создать один смарт-компонент (корневой компонент), который содержит бизнес-логику и логику поведения, передавать состояния через входные свойства и вызывать действия, прослушивающие выходные параметры. Таким образом, маленькие компоненты действительно предназначены только для целей рендеринга. Конечно, наш корневой компонент должен иметь некоторые сервисные вызовы внутри, и мы не можем просто удалить их, но его полезность будет ограничена только бизнес-логикой, а не рендерингом.
Давайте посмотрим на пример компонента счетчика. Счетчик — это компонент с двумя кнопками, увеличивающими или уменьшающими значение, и одним displayField , отображающим currentValue значение. Таким образом, мы получаем четыре компонента:
- Контейнер Контейнера
- кнопка увеличения
- Кнопка уменьшения
- Текущая стоимость
Вся логика находится внутри CounterContainer , так что все три — просто рендереры. Вот код для трех из них:
@Component({ selector: 'decrease-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Decrease </button>`, }) export class DecreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); } @Component({ selector: 'current-value', template: `<button> {{ currentValue }} </button>`, }) export class CurrentValueComponent { @Input() currentValue!: string; } @Component({ selector: 'increase-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Increase </button>`, }) export class IncreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); }Посмотрите, какие они простые и чистые. У них нет состояния или побочных эффектов, они просто зависят от входных свойств и исходящих событий. Представьте, как легко их проверить. Мы можем назвать их чистыми компонентами, поскольку они на самом деле таковыми являются. Они зависят только от входных параметров, не имеют побочных эффектов и всегда возвращают одно и то же значение (строку шаблона) при передаче одних и тех же параметров.

Таким образом, чистые функции в функциональном программировании переносятся в чистые компоненты в Angular. Но где вся логика? Логика осталась, но немного в другом месте, а именно в CounterComponent .
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { @Input() disabled!: boolean; currentValue = 0; get decreaseIsDisabled() { return this.currentValue === 0; } get increaseIsDisabled() { return this.currentValue === 100; } constructor() {} ngOnInit(): void {} decrease() { this.currentValue -= 1; } increase() { this.currentValue += 1; } } Как видите, логика поведения живет в CounterContainer , но часть рендеринга отсутствует (она объявляет компоненты внутри шаблона), потому что часть рендеринга предназначена для чистых компонентов.
Мы можем внедрить столько сервиса, сколько захотим, потому что здесь мы обрабатываем все манипуляции с данными и изменения состояния. Стоит упомянуть одну вещь: если у нас есть глубоко вложенный компонент, мы не должны создавать только один компонент корневого уровня. Мы можем разделить его на более мелкие смарт-компоненты и использовать тот же шаблон. В конечном счете, это зависит от сложности и уровня вложенности каждого компонента.
Мы можем легко перейти от этого шаблона к самой библиотеке NgRx, которая находится всего на один уровень выше.
Библиотека NgRx
Мы можем разделить любое веб-приложение на три основные части:
- Бизнес-логика
- Состояние приложения
- Логика рендеринга
Бизнес-логика — это все поведение, которое происходит с приложением, такое как сеть, ввод, вывод, API и т. д.
Состояние приложения — это состояние приложения. Он может быть глобальным, как текущий авторизованный пользователь, а также локальным, как текущее значение компонента счетчика.
Логика рендеринга включает в себя рендеринг, например, отображение данных с помощью DOM, создание или удаление элементов и т. д.
Используя шаблон Тупой-Умный, мы отделили логику рендеринга от бизнес-логики и состояния приложения, но мы также можем разделить их, потому что они концептуально отличаются друг от друга. Состояние приложения похоже на снимок вашего приложения в текущее время. Бизнес-логика похожа на статическую функциональность, которая всегда присутствует в вашем приложении. Самая важная причина их разделения заключается в том, что бизнес-логика в основном является побочным эффектом, которого мы хотим избежать в коде приложения, насколько это возможно. Это когда библиотека NgRx с ее функциональной парадигмой сияет.
С NgRx вы разделяете все эти части. Есть три основные части:
- Редукторы
- Действия
- Селекторы
В сочетании с функциональным программированием все три вместе дают нам мощный инструмент для работы с приложениями любого размера. Давайте рассмотрим каждый из них.
Редукторы
Редуктор — это чистая функция с простой сигнатурой. Он принимает старое состояние в качестве параметра и возвращает новое состояние, полученное либо из старого, либо из нового. Само состояние — это единый объект, который живет вместе с жизненным циклом вашего приложения. Это как HTML-тег, единственный корневой объект.
Вы не можете напрямую изменить объект состояния, вам нужно изменить его с помощью редукторов. Это имеет ряд преимуществ:
- Логика изменения состояния находится в одном месте, и вы знаете, где и как изменяется состояние.
- Функции редуктора — это чистые функции, которые легко тестировать и ими легко управлять.
- Поскольку редьюсеры — это чистые функции, их можно запоминать, что позволяет кэшировать их и избегать дополнительных вычислений.
- Изменения состояния неизменны. Вы никогда не обновляете один и тот же экземпляр. Вместо этого вы всегда возвращаете новый. Это позволяет «путешествовать во времени» отладки.
Это тривиальный пример редуктора:
function usernameReducer(oldState, username) { return {...oldState, username} }Несмотря на то, что это очень простой фиктивный редьюсер, он является основой для всех длинных и сложных редьюсеров. Все они имеют одинаковые преимущества. В нашем приложении могут быть сотни редукторов, и мы можем сделать столько, сколько захотим.
Для нашего компонента счетчика наше состояние и редукторы могут выглядеть так:
interface State{ decreaseDisabled:boolean; increaseDisabled:boolean; currentValue:number; } const MIN_VALUE=0; const MAX_VALUE =100; function decreaseReducer(oldState) { const newValue = oldState.currentValue -1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MIN_VALUE } function increaseReducer(oldState) { const newValue = oldState.currentValue + 1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MAX_VALUE }Мы удалили состояние из компонента. Теперь нам нужен способ обновить наше состояние и вызвать соответствующий редьюсер. Вот когда в игру вступают действия.
Действия
Действие — это способ уведомить NgRx о вызове редуктора и обновлении состояния. Без этого было бы бессмысленно использовать NgRx. Действие — это простой объект, который мы присоединяем к текущему редюсеру. После его вызова будет вызван соответствующий редьюсер, поэтому в нашем примере у нас могут быть следующие действия:
enum CounterActions { IncreaseValue = '[Counter Component] Increase Value', DecreaseValue = '[Counter Component] Decrease Value', } on(CounterActions.IncreaseValue,increaseReducer); on(CounterActions.DecreaseValue,decreaseReducer);Наши действия привязаны к редюсерам. Теперь мы можем дополнительно модифицировать наш компонент-контейнер и при необходимости вызывать соответствующие действия:
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Примечание. Мы удалили состояние и вскоре добавим обратно .
Теперь наш CounterContainer не имеет никакой логики изменения состояния. Он просто знает, что отправлять. Теперь нам нужен способ отображения этих данных в представлении. Это полезность селекторов.
Селекторы
Селектор также является очень простой чистой функцией, но, в отличие от редуктора, он не обновляет состояние. Как следует из названия, селектор просто выбирает его. В нашем примере у нас может быть три простых селектора:
function selectCurrentValue(state) { return state.currentValue; } function selectDicreaseIsDisabled(state) { return state.decreaseDisabled; } function selectIncreaseIsDisabled(state) { return state.increaseDisabled; } Используя эти селекторы, мы могли бы выбрать каждый фрагмент состояния внутри нашего смарт-компонента CounterContainer .
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="ecreaseIsDisabled$ | async" (decrease)="decrease()" > </decrease-button> <current-value [currentValue]="currentValue$ | async"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled$ | async" > </increase-button> `, }) export class CounterContainerComponent implements OnInit { decreaseIsDisabled$ = this.store.select(selectDicreaseIsDisabled); increaseIsDisabled$ = this.store.select(selectIncreaseIsDisabled); currentValue$ = this.store.select(selectCurrentValue); constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Эти выборки по умолчанию асинхронны (как и Observables в целом). Это не имеет значения, по крайней мере, с точки зрения шаблона. То же самое верно и для синхронного, так как мы просто выбираем что-то из нашего состояния.
Давайте сделаем шаг назад и посмотрим на общую картину, чтобы увидеть, чего мы достигли на данный момент. У нас есть приложение Counter, состоящее из трех основных частей, почти не связанных друг с другом. Никто не знает, как состояние приложения управляет само собой или как уровень рендеринга отображает состояние.
Разделенные части используют мост (Действия, Селекторы) для соединения друг с другом. Они развязаны до такой степени, что мы можем взять весь код государственного приложения и перенести его в другой проект, например, в мобильную версию. Единственное, что нам нужно будет реализовать, это рендеринг. Но как насчет тестирования?
По моему скромному мнению, тестирование — лучшая часть NgRx. Тестирование этого примера проекта сродни игре в крестики-нолики. Есть только чистые функции и чистые компоненты, поэтому их тестирование не составляет труда. А теперь представьте, если этот проект станет больше, с сотнями компонентов. Если мы будем следовать той же схеме, мы просто будем добавлять все больше и больше кусочков вместе. Он не стал бы беспорядочным, нечитаемым куском исходного кода.
Мы почти закончили. Осталось обсудить только одну важную вещь: побочные эффекты. До сих пор я много раз упоминал о побочных эффектах, но не стал объяснять, где их хранить.
Это потому, что побочные эффекты — это вишенка на торте, и, создав этот шаблон, их очень легко удалить из кода приложения.
Побочные эффекты
Допустим, в нашем приложении-счетчике есть таймер, и каждые три секунды он автоматически увеличивает значение на единицу. Это простой побочный эффект, который должен где-то жить. Это тот же побочный эффект, по определению, что и запрос Ajax.
Если мы подумаем о побочных эффектах, у большинства из них есть две основные причины существования:
- Делать что-либо за пределами государственной среды
- Обновление состояния приложения
Например, сохранение некоторого состояния в LocalStorage — это первый вариант, а обновление состояния из ответа Ajax — второй. Но они оба имеют одну и ту же подпись: у каждого побочного эффекта должна быть какая-то отправная точка. Его нужно вызвать хотя бы один раз, чтобы он начал действие.
Как мы уже отмечали ранее, в NgRx есть хороший инструмент для передачи кому-либо команды. Это действие. Мы могли бы вызвать любой побочный эффект, отправив действие. Псевдокод может выглядеть так:
function startTimer(){ setInterval(()=>{ console.log("Hello application"); },3000) } on(CounterActions.StartTime,startTimer) ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);Это довольно тривиально. Как я упоминал ранее, побочные эффекты либо что-то обновляют, либо нет. Если побочный эффект ничего не обновляет, делать нечего; мы просто оставляем это. Но если мы хотим обновить состояние, как нам это сделать? Точно так же компонент пытается обновить состояние: вызывает другое действие. Итак, мы вызываем действие внутри побочного эффекта, которое обновляет состояние:
function startTimer(store) { setInterval(()=> { // We are dispatching another action dispatch(CounterActions.IncreaseValue) }, 3000) } on(CounterActions.StartTime, startTimer); ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);Теперь у нас есть полнофункциональное приложение.
Подводя итоги нашего опыта NgRx
Есть несколько важных тем, которые я хотел бы упомянуть, прежде чем мы закончим наше путешествие по NgRx:
- Показанный код — это простой псевдокод, который я придумал для статьи; он подходит только для демонстрационных целей. NgRx — это место, где живут настоящие исходники.
- Официального руководства, подтверждающего мою теорию о соединении функционального программирования с библиотекой NgRx, не существует. Это всего лишь мое мнение, сформировавшееся после прочтения десятков статей и примеров исходников, созданных высококвалифицированными людьми.
- После использования NgRx вы обязательно поймете, что это намного сложнее, чем этот простой пример. Моя цель состояла не в том, чтобы сделать его проще, чем он есть на самом деле, а в том, чтобы показать вам, что, хотя это немного сложно и может даже привести к более длинному пути к месту назначения, это стоит дополнительных усилий.
- Худшее использование NgRx — использовать его везде, независимо от размера или сложности приложения. В некоторых случаях вам не следует использовать NgRx; например, в формах. Практически невозможно реализовать формы внутри NgRx. Формы приклеиваются к самому DOM; они не могут жить отдельно. Если вы попытаетесь разделить их, вы обнаружите, что ненавидите не только NgRx, но и веб-технологии в целом.
- Иногда использование одного и того же шаблонного кода даже для небольшого примера может превратиться в кошмар, даже если это может принести нам пользу в будущем. Если это так, просто интегрируйтесь с другой замечательной библиотекой, которая является частью экосистемы NgRx (ComponentStore).
