Реактивне програмування з RxJS та Angular

Дмитро
Дмитро
Developer Python, PHP, Javascript
4.3
04.10.2019
18943
0
Зміст:
  1. Висновки
25 минут

У цій статті ми розглянемо основні принципи та механізми розробки програм з використанням методик реактивного програмування. Ця парадигма програмування з'явилася відносно недавно і орієнтована на потоки даних та поширення змін. Коротко це спосіб написання програмного коду, заснованого на реакціях на певні події, що виникають всередині додатка. При цьому ми повинні чітко розділяти дані на динамічні та статичні, і наша модель має автоматично поширювати зміни динамічних даних за допомогою потоків. Реактивне програмування передбачалося як легкий шлях для створення інтерфейсів користувача, анімації та будь-яких інших процесів, що змінюються з часом. Реактивне програмування може бути здійснено кількома підходами:

Давайте обговоримо Ваш проєкт
article-order-form__collapsed-text
Phone
Натискаючи кнопку “Відправити”, ви даєте згоду на обробку особистих даних. Детальніше
  • імперативним програмуванням;
  • об'єктно-орієнтованим;
  • функціональним.

Однак найбільш природним базисом для реактивного програмування та роботою з реактивними структурами даних є функціональний підхід.

Найбільш популярною бібліотекою, що використовує цей підхід, є RxJS , яка була взята за основу такого фреймворку як Angular.

RxJS – це Javascript бібліотека для трансформації, складання та вилучення асинхронних потоків даних. Вона може бути використана як у браузері, так і стороні сервера.

Отже, що таке реактивне програмування і що означає «мислити реактивно»? Це така парадигма, коли написання програмного коду передбачає роботу з асинхронним потоком даних і опис логіки реакцію їх зміни на відміну імперативного підходу, де ми явно обслуговуємо їх зміна.

При реактивному підході, будь-який перерахований або композитний тип даних, як список, розглядається як потік і може бути представлений наступним чином:

image1

Будь-який потік на тимчасовій шкалі має початок, набір значень, що послідовно випускаються, і кінець, що позначається вертикальною лінією. Таким чином, потік генерує нам події, упорядковані в часі. Цими подіями може бути будь-що: клік на кнопці, http-запит на сервер, дані, що надходять через сокет-з'єднання, анімація і т.д.

І ми маємо можливість прослуховувати такий потік та відповідно реагувати на ці події. Використовуючи звичайні функції, ми можемо комбінувати, змінювати та фільтрувати такі потоки.

Потік може випускати три види подій:

  • значення;
  • помилка;
  • завершення.

І наше завдання їх перехопити та описати реакцію на ці три види асинхронних (або синхронних) подій. При цьому ми працюватимемо з кількома програмними сутностями, перша з яких – спостережуваність .


Спостережуваність (Observable) – це звичайна функція з кількома специфічними нею характеристиками.

Як параметр вона приймає об'єкт-спостерігач ( observer ). Цей спостерігач описує 3 методи:

  • Next – отримання чергового значення із потоку;
  • Error – помилка;
  • Complete – подія завершення потоку.

Спостережуваність забезпечує механізм передачі повідомлень між тим, хто генерує дані і тим, хто їх використовує.

При цьому існує 2 види стратегії поведінки між виробником та споживачем даних.


При першій ( pull-стратегії ) місце та час реакції на зміну або отримання даних визначається споживачем , при другій ( push-стратегії ) – виробником .

Прикладом pull-стратегії є звичайний виклик функції з того місця програми, де потрібні ті дані, які вона повертає, при цьому сама функція нічого не знає про те, хто і звідки її «смикатиме».

При push-стратегії – навпаки, функція-виробник як би «обв'язується» оброблювачами (споживачами), які нічого не знають про те, де і коли ці дані будуть згенеровані.

RxJS використовує другу push -стратегію.

Спостережуваність лінива. Вона не починає видавати значень, поки хтось не підпишеться на неї функцією subscribe на відміну від промісів.

Метод subscribe() повертає об'єкт типу передплатник (subscription) , який у собі має функцію unsubscribe() , що відписує його від спостережуваності та скасовує подальшу генерацію подій, чого також немає у промісів.

Observable-об'єкт подібний до функції з відсутністю аргументів, але здатної приймати і повертати безліч значень на відміну від звичайних функцій.

Бібліотека RjJS надає низку інструментів, які полегшують створення спостережень із різних елементів системи, таких як події, проміси, таймери тощо. Наведемо низку прикладів створення Observable-об'єктів із різних джерел.

Створення з допомогою конструктора.

import { Observable } from 'rxjs';
const observable = new Observable(function subscribe(subscriber) {
const id = setInterval(() => {
subscriber.next('hi')
}, 1000);
})

Найчастіше об'єкти Observable створюються за допомогою спеціальних функцій-фабрик або операторів (of, from, interval тощо), які повертають об'єкти Observable із переданих їм аргументів. При підписці на них ми передаємо функцію-обробник, яка приймає дані, що генеруються потоком, і обробляє їх.


Оператори RXJS.


Оператори імпортуються з пакета rxjs і служать для створення, керування та зміни Observable-об'єктів. Існує два види операторів: потокові (pipeable) та творчі (creation). Розглянемо найбільш широко використовувані з них.

Функції of, from, fromEvent.

Ці оператори ставляться до творчих і викликаються як самостійні функції створення нових Observable-объектов з переданих аргументів.

Наступний приклад демонструє створення Observable-об'єкта зі списку за допомогою функції from c наступною передплатою та відпискою.

import { from } from 'rxjs';

const observable = from([10, 20, 30]);
const subscription = observable.subscribe(x => console.log(x));
subscription.unsubscribe()

На відміну від функції of(), функція from() приймає аргументи різних видів (проміси, списки тощо), перетворюючи їх (якщо необхідно) на об'єкти Observable, куди можна передплатити. Функція of() приймає значення та повертає їх потоком без перетворення. Ще одна важлива відмінність у тому, як ці аргументи обробляються.

 of([1,2,3]).subscribe( x => {
console.log(x);
});

from([1,2,3]).subscribe( x => {
console.log(x);
})

У наведеному вище прикладі, в першому випадку потік зайде весь масив, коли в другому будуть послідовно згенеровані його елементи. За допомогою функції from ми можемо згенерувати Observable з промісу.

const promiseSource = from(new Promise(resolve => resolve('Hello World!')));
const subscribe = promiseSource.subscribe(val => console.log(val))

Функція fromEvent дозволяє згенерувати Observable з події за переданим джерелом подій, наприклад посиланням, кнопкою або об'єктом типу EventEmitter.

У прикладі нижче ми створюємо в шаблоні кнопку, позначаючи її якорем #mybutton, щоб мати можливість вибрати її в компоненті за допомогою декоратора ViewChild.

cod_1

Потім створюємо потік Observable за переданим елементом DOM.

import { Component, ViewChild, OnInit} from '@angular/core';
import { fromEvent } from 'rxjs';

@Component({
...
})
export class AppComponent implements OnInit{

@ViewChild('mybutton') button;

ngOnInit(){
fromEvent(this.button.nativeElement, 'click').subscribe(evt => {
console.log(evt);
}
);

Функції map і filter .

Функція map працює аналогічно однойменному методу списку, але оперує Observable-об'єктом для генерації значень потоку. Наприклад, прохід по масиву за допомогою способу переліку map

[1, 2, 3].map(x => x * x)

Може бути переписаний за допомогою Observable.

map(x => x * x)(of(1, 2, 3)).subscribe((v) => {...});

Оператор map() приймає функцію, яка послідовно застосовується у всіх емітованих значеннях Observable потоку-джерела. Таким чином він використовується для того, щоб змінювати елементи потоку, на відміну від іншого оператора filter(), який також приймає функцію, яку застосовує до кожного елемента, але при цьому не змінює, а повертає незмінений елемент потоку залежно від того, чи дотримується умова переданої їй функції. Таким чином, ми маємо можливість прибрати (відфільтрувати) непотрібні нам елементи з потоку.

Наступний приклад вилучить зі списку непарний числа.

from([1, 2, 3, 4, 5]).pipe(filter(num => num % 2 === 0)).subscribe(x => console.log(x));

Оскільки оператор filter() є потоковим (pipeable), ми використовуємо його всередині функції pipe() , про яку йдеться нижче.

Потокові (pipeable) оператори служать зміни потоку, чи створення нового з існуючого і використовують усередині конструкції pipe.

observableInstance.pipe(operator())

До них належать такі оператори: filter(), mergeMap() та switchMap() . Коли вони застосовуються, то не змінюють оригінальний Observable-об'єкт, натомість вони повертають новий Observable-об'єкт, який містить ту ж логіку підписки що і оригінал.

Використовуючи оператор filter(), можна фільтрувати непотрібні або залишати потрібні елементи потоку. Наприклад, у цьому прикладі ми вибираємо ті об'єкти користувачів вік яких більше або дорівнює 30.

const source = from([{ name: 'Joe', age: 31 }, { name: 'Bob', age: 25 }]);
const example = source.pipe(filter(person => person.age >= 30));
const subscribe = example.subscribe(val => console.log(`Over 30: $`))

Оператори mergaMap() та switchMap().

Цей оператор збирає дані з кількох потоків (Observable об'єктів) до одного потоку. При цьому ці потоки можуть не завершуватися і продовжувати виконання, що може призвести до витоків. Оператор switchMap() також збирає дані з кількох потоків, але при цьому залишає активним (підписаним) тільки один - останній.

Оператор switchMap() корисний у тих сценаріях, коли нам не важливі дані запитів, які надійшли раніше останнього. Тому цей оператор скасовуватиме підписки всіх раніше надійшли запитів.


Наступний приклад оновлює лічильник після кожного кліку після 1 сек.

 fromEvent(this.button.nativeElement, 'click')
.pipe(
switchMap((e: any) => interval(1000))
)
.subscribe(e => console.log(e))

Висновки

У цій статті розглянуто базові принципи програмування на основі Observable-об'єктів із застосуванням бібліотеки RxJS. Розкрито суть реактивного підходу до програмування та використання потоків даних для створення додатків, що реагують на зміни їхнього стану. Висвітлено роботу найбільш широко використовуваних операторів бібліотеки.

Як вам стаття?
4.3
Проголосувало: 31
Давайте обговоримо Ваш проєкт
article-order-form__collapsed-text
Phone
Натискаючи кнопку “Відправити”, ви даєте згоду на обробку особистих даних. Детальніше
Звернути
Коментарі
(0)
Будьте першими, хто залишить коментар
have questions image
Залишились питання?
Залиште контактні дані. Наш менеджер зв'яжеться та проконсультує вас.
Підписуйтесь на розсилку Айтижблог
blog subscriber decor image
Бажаєте отримувати цікаві статті?
Натискаючи кнопку “Відправити”, ви даєте згоду на обробку особистих даних. Детальніше
Слідкуйте за нами у соціальних мережах