Реактивные приложения на Angular/NGRX. Часть 3. Effects.
В третьей части серии статей мы поговорим про side-effect-ы:
Для того, чтобы понять что такое эффекты, рассмотрим следующую ситуацию. Мы создали стору для фильмов. Когда пользователь хочет внести новый фильм в список, мы получаем payload из вьюшки и установленный тип действия в компоненте контейнере. Тут все просто. Но как нам сделать, допустим, загрузку данных с сервера и комбинировать ее с redux? Посмотрим на следующий диалог двух разработчиков:
— Легко! Сделаем сервис в котором создадим функцию получения данных http.get(...)
, вызвав ее в компоненте и подписавшись на результат, когда данные придут отправляем action и заполняем данными стору. ГОТОВО!
— Хорошо, теперь посмотрим на наш компонент. В предыдущих статьях мы говорили, что компоненты контейнеры могут только принимать данные из сторы, передавать их stateless-компонентам и отправлять действия о каком-либо событии для того, чтобы мы могли отслеживать изменения. Они чистые! Это позволяет нам отключать холостые change detection-ы. И в данном случае наш компонент зависит от сервиса получающего данные (а не напрямую от сторы) там же располагается какая-то условная отправка action-ов не через UI.
— И что!? Работает же! Оставим так!
— Окей, окей, давай оставим. Только другой разработчик решит заинжектить стору сразу в сервис, там же вызвать метод http.get(...)
, там же подпишется и там же отправить action. И это тоже работает!
— Ну да! Правда… Я предпочел бы сделать это в компоненте!
— Вот именно! Все мыслят по разному в зависимости от уровня разработки, опыта и предпочтений. В итоге может получится Парк Аттракционов!
Именно поэтому нам нужно одно условное место, где мы будем хранить наши сайд-эффекты.
Side-effects
— Эффекты! Эффекты! Что это вообще такое?
— Отличный вопрос! Все имеют собственное мнение на этот счет и существует множество дискуссий в которые мне не очень хочется вдаваться. Поэтому сначала перечислим некоторые из них:
- Обращение к серверу (ajax)
- Запись\чтение local\session storage
- Вывод в console
- Изменение каких-либо глобальных переменных (больше относится к функциональному программированию, но и тут имеет место быть)
Если обобщить, то чаще всего это обращение к browser API и изменение каких-либо неконтекстных (глобальных) переменных, от которых зависит результат каких-либо функций или операций.
Ещё раз - когда мы обращаемся к серверу для принятия данных, записываем или берем в\из localstorage токен и т.д. — все это является side-эффектами
Ссылаясь на первую часть серии статей, хочу напомнить, что redux предполагает, что наш View, то есть компоненты, умеют только dispatch-ить (отправлять) action-ы. Поэтому все вычисления, запросы к бекенду и другим браузерным API — мы будем держать в наших эффектах. И примите Вы низкий поклон от верстальщиков, работающими с angular😇. Pure components — no confusion.
How does it works?
Итак, зачем они нужны мы разобрались. Теперь подумаем как их запустить или как они вообще работают.
Будем исходить из того, что у нас есть action-ы, которые мы отправляем из компонентов. И логично предположить, что если мы их как-то перехватим, то сможем отреагировать подходящим для нас образом. Именно так и устроен ngrx\effects.
Мы просто подписываемся на определенные action-ы.
Выше Вы можете увидеть что action сначала попадает в reducer и только потом в effect-ы, этот вопрос изводил многих моих знакомых, но важно знать порядок выполнения.
Когда action отправлен, наступает то самое время для наших асинхронных делишек 😈! Почему асинхронных? Потому что код будет выполнен только тогда, когда нашему observabl-у дадут волшебный пинок next-ом.
Каждый dispatch преобразуется в observable, который так же возвращает observable нового action-а. Другими словами по дефолту эффекты ожидают, что мы будем возвращать новый action observable. Но это конфигурируется в декораторе:
dispatch:false
Practice!
Дефолтная установка:
npm install @ngrx/effects
или yarn add @ngrx/effects
Для демонстрации создадим небольшой сервис с методом, который будет выполнять http запрос и возвращать поток с данными.
Теперь реализуем непосредственно эффект. Тут я подразумеваю, что у вас есть два действия:
- Инициализация загрузки (ее мы будем слушать в эффектах)
- Успешный ответ сервера (описан в reducer-е)
Итак, мы инжектим класс Actions, представляющий все action-ы в виде Observable и наш сервис, где далее методом ofType подписываемся только на те action-ы, которые передаем в эту функцию. В данном случае мы “слушаем” LOAD_MOVIE.
Так же важно добавлять декоратор @Effect(), который обеспечивает метаданные для наших эффектов, регистрирующих их как новый источник action-ов.
Когда действие отправляется, оно также попадает в наш эффект, где мы можем взять payload (если он есть) и совершить какие-то действия. Тут мы заменяем начальный поток на новый с помощью switchMap и возвращаем его (функцией getMovie), так же мапимся, извлекая данные и возвращаем новый action. Все просто 😎.
Далее подключим сам модуль Effects и передадим наши эффекты как аргумент:
Отлично! Теперь мы понимаем механизм действия эффектов. В следующих статьях можем разобрать пару распространенных кейсов в эффектах, так что оставляйте комментарии, что бы Вы хотели разобрать.
Полезные ссылки:
- Официальный репозиторий — https://github.com/ngrx/platform
- Угловатый Redux. Реактивные приложения на Angular\NGRX
Можете подписаться, чтобы не пропустить следующие статьи и не забываем делать claps— так я буду знать, что материал оказался полезным для Вас!