Управление состоянием с Akita в Angular
Зачем вообще нужно управлять состоянием?
Веб-приложения постоянно меняют свое состояние. Например, когда пользователь вводит текст в поле формы, выбирает элемент в списке или открывает новую страницу - состояние приложения обновляется.
Чтобы отслеживать все эти изменения, нужен специальный механизм управления состоянием. Он позволяет:
- Хранить данные в определенном месте (например, в специальных объектах - сторах)
- Обновлять эти данные по определенным правилам
- Получать актуальные данные в любой момент
- Автоматически обновлять UI при изменении данных
- Отменять изменения и возвращать предыдущее состояние
Без управления состоянием код быстро превращается в хаос из глобальных переменных, сложных зависимостей и дублирования данных. Поэтому использование спецальных инструментов вроде Akita - это очень хорошая практика.
Что такое Akita?
Akita - это библиотека для управления состоянием в Angular-приложениях. Вот её основные преимущества:
- Простота. Интуитивный и лаконичный API.
- Надежность. Строго типизированное состояние, иммутабельность.
- Гибкость. Кастомизируй под свои нужды.
- Производительность. Оптимизирована для скорости.
- Масштабируемость. Подходит как для маленьких, так и для крупных приложений.
То есть с Akita ты можешь быстро организовать управление состоянием в своём приложении и не париться по поводу производительности или масштабирования. Дальше разберём как это делается.
Установка Akita
Чтобы начать использовать Akita, нужно выполнить deux простых шага:
npm install @datorama/akita --save npm install @datorama/akita-ng-entity-service --save
2. Импортировать модули Akita в app.module.ts:
import { AkitaNgDevtools } from '@datorama/akita-ngdevtools';
import { AkitaNgRouterStoreModule } from '@datorama/akita-ng-router-store';
import { AkitaTypedFormModule } from '@datorama/akita-ng-forms';
@NgModule({
declarations: [AppComponent],
imports: [
AkitaNgDevtools.forRoot(),
AkitaNgRouterStoreModule.forRoot(),
AkitaTypedFormModule
],
bootstrap: [AppComponent]
})
export class AppModule {}Вот и всё! Теперь Akita готова к использованию в твоём приложении. Давай разберём как это делать на практике.
Создание Store
Основа управления состоянием в Akita - это Store. Store похож на обычный JavaScript объект, но с некоторыми дополнительными свойствами:
- Хранит данные в строго типизированном виде
- Позволяет изменять данные только через специальные методы
- Может отслеживать изменения и выполнять действия при обновлении
- Даёт доступ к данным через удобные запросы
Давай создадим простой Store для хранения списка товаров в корзине:
import { Injectable } from '@angular/core';
import { Store, StoreConfig } from '@datorama/akita';
export interface CartState {
items: string[];
}
export function createInitialState(): CartState {
return {
items: []
};
}
@Injectable({ providedIn: 'root' })
@StoreConfig({ name: 'cart' })
export class CartStore extends Store<CartState> {
constructor() {
super(createInitialState());
}
}- Описываем интерфейс для состояния
- Создаём initialState - начальное состояние
- Создаём класс Store, наследуя от базового класса Akita
- В конструктор передаём начальное состояние
Теперь у нас есть store для хранения данных о корзине! Давай посмотрим как им пользоваться.
Действия и мутации
Чтобы изменить данные в store, нужно выполнить действие и совершить мутацию.
Действие - это просто функция, которая описывает какие изменения нужно сделать.
Например, добавим действие для добавления товара в корзину:
export class CartStore extends Store<CartState> {
addItem(item: string) {
this.update(state => {
return {
items: [...state.items, item]
};
});
}
}Мутация - это функция, которая на самом деле модифицирует состояние.
В нашем случае это метод update из базового класса Store. Он принимает callback, который возвращает новое состояние.
Таким образом мы добавляем новый товар в массив items. При этом старый массив не мутируется, т.к создаётся новый с использованием оператора spread. Это очень важно для производительности и предсказуемости!
Чтобы вызвать это действие, делаем так:
this.cartStore.addItem('Новый товар');Теперь данные в store обновятся иммутабельным способом!
Запросы к хранилищу
Чтобы получить данные из store, используются селекторы.
Селектор - это просто функция, которая принимает состояние и возвращает нужную часть:
export class CartStore extends Store<CartState> {
items$ = this.select(state => state.items);
}А в компоненте достаточно подписаться на этот селектор:
constructor(private cartStore: CartStore) {}
ngOnInit() {
this.items$ = this.cartStore.items$;
}И использовать в шаблоне через async pipe:
<div *ngFor="let item of items$ | async">
{{ item }}
</div>Akita автоматически отследит изменения в store и обновит данные в шаблоне! Очень удобно и эффективно.
Асинхронные операции
А что если нужно выполнить асинхронный запрос при обновлении store? Например, сохранить корзину на сервере?
Для этого есть метод setLoading(true) который позволяет отследить загрузку:
saveCart() {
this.cartStore.setLoading(true);
this.http.post('/save', this.cartStore.getValue()).pipe(
tap(() => this.cartStore.setLoading(false))
);
}А в компоненте можно отслеживать loading$:
<div *ngIf="loading$ | async"> Saving...</div>
Таким образом мы можем элегантно обрабатывать асинхронные операции и обновлять UI accordingly.
Пример использования в проекте
Давай я покажу как мы использовали Akita в одном из своих проектов.
У нас был интернет магазин, и нам нужно было реализовать корзину и списки желаний. Вот как мы это сделали:
1. Создали CartStore для хранения товаров в корзине:
export interface CartState {
items: CartItem[];
}
export class CartStore extends Store<CartState> {
// ...действия, мутации, запросы
}2. Создали WishListStore для хранения избранных товаров:
export interface WishListState {
items: string[];
}
export class WishListStore extends Store<WishListState> {
// ...
}3. В сервисе для работы с API делали запросы и обновляли store:
updateCart(items) {
this.cartStore.update(state => ({
items
}));
}
addToWishList(product) {
this.wishListStore.addItem(product);
}4. В компонентах подписывались на нужные селекторы и отображали данные.
5. Для общих данных использовали селекторы из EntityService.
Таким образом мы разделили всю логику на небольшие части и создали чистое управление состоянием!
Итог
- Зачем нужно управлять состоянием в приложениях
- Как использовать Akita для этого в Angular
- Как создавать Store, выполнять действия и мутации
- Как делать запросы и обрабатывать асинхронные операции
- Пример интеграции Akita в реальном проекте
Главное - Akita позволяет организовать предсказуемое и эффективное управление состоянием без лишней сложности. А это очень важно для современных веб-приложений! Надеюсь, эта статья была полезна и понятна.