Rеактивное программирование позволяет описывать поведение интерфейса как поток данных, где изменения состояния автоматически отражаются на представлении. Riot.js, как лёгкий фреймворк для создания компонентов, отлично интегрируется с реактивными паттернами, а использование RxJS значительно расширяет возможности управления асинхронными событиями и потоками данных.
Компоненты Riot.js строятся на основе HTML-шаблонов, JavaScript и
реактивных свойств. Ключевым механизмом является реактивность
состояния. Любое изменение свойства, объявленного через
this, автоматически обновляет DOM:
<counter>
<p>Счётчик: { count }</p>
<button oncl ick={ increment }>Увеличить</button>
<script>
this.count = 0;
this.increment = () => {
this.count += 1;
}
</script>
</counter>
Здесь count — реактивное свойство. Riot.js отслеживает
его изменения и перерисовывает шаблон без дополнительных вызовов.
RxJS (Reactive Extensions for JavaScript) предоставляет инструменты для работы с обсерваблами (Observable) — потоками данных, на которые можно подписываться. В сочетании с Riot.js это позволяет строить сложные асинхронные сценарии, например, динамическую загрузку данных, фильтрацию или дебаунс ввода пользователя.
import { fromEvent } from 'rxjs';
import { map, debounceTime } from 'rxjs/operators';
<search-box>
<input id="query" placeholder="Введите запрос" />
<ul>
<li each={ item in results }>{ item }</li>
</ul>
<script>
this.results = [];
const input = document.getElementById('query');
fromEvent(input, 'input')
.pipe(
map(e => e.target.value),
debounceTime(300)
)
.subscribe(query => {
// Симуляция запроса к API
this.results = fetchResults(query);
});
function fetchResults(query) {
return query ? [`Результат 1: ${query}`, `Результат 2: ${query}`] : [];
}
</script>
</search-box>
В этом примере:
fromEvent создаёт Observable из событий ввода.pipe применяет цепочку операторов: map для
извлечения значения инпута, debounceTime для уменьшения
частоты вызовов.subscribe обновляет реактивное свойство
results, что автоматически перерисовывает список в
DOM.RxJS позволяет объединять несколько потоков с помощью операторов
merge, combineLatest, switchMap и
других, создавая сложные реактивные сценарии.
import { fromEvent, combineLatest } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
<filter-app>
<input id="search" placeholder="Поиск..." />
<SELECT id="category">
<option value="">Все категории</option>
<option value="books">Книги</option>
<option value="movies">Фильмы</option>
</select>
<ul>
<li each={ item in filtered }>{ item.name }</li>
</ul>
<script>
this.filtered = [];
const searchInput = document.getElementById('search');
const categorySelect = document.getElementById('category');
const search$ = fromEvent(searchInput, 'input').pipe(
map(e => e.target.value),
startWith('')
);
const category$ = fromEvent(categorySelect, 'change').pipe(
map(e => e.target.value),
startWith('')
);
combineLatest([search$, category$]).subscribe(([searchTerm, category]) => {
this.filtered = filterItems(searchTerm, category);
});
function filterItems(searchTerm, category) {
const items = [
{ name: 'Книга 1', category: 'books' },
{ name: 'Фильм 1', category: 'movies' },
{ name: 'Книга 2', category: 'books' },
];
return items.filter(item =>
(!category || item.category === category) &&
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}
</script>
</filter-app>
Здесь combineLatest объединяет два потока: значения из
поля поиска и выбора категории. Любое изменение одного из потоков
автоматически пересчитывает фильтрованные данные.
BehaviorSubject позволяет хранить текущее состояние
потока и подписываться на него, обеспечивая мгновенную реактивность при
изменении данных:
import { BehaviorSubject } FROM 'rxjs';
<counter-subject>
<p>Счётчик: { count }</p>
<button oncl ick={ increment }>+</button>
<button oncl ick={ decrement }>-</button>
<script>
const count$ = new BehaviorSubject(0);
this.count = 0;
count$.subscribe(value => {
this.count = value;
});
this.increment = () => count$.next(count$.getValue() + 1);
this.decrement = () => count$.next(count$.getValue() - 1);
</script>
</counter-subject>
BehaviorSubject хранит текущее значение
count, а подписка автоматически обновляет реактивное
свойство компонента.
switchMap
предотвращает гонки запросов, гарантируя, что результат отображается
только для последнего запроса.combineLatest, merge)
и управление состоянием через BehaviorSubject упрощает построение
интерфейсов с динамическим поведением.Использование реактивных паттернов в сочетании с Riot.js и RxJS создаёт мощную архитектуру для современных веб-приложений, обеспечивая предсказуемость, масштабируемость и чистоту кода.