От автора: каждый разработчик знает, что состояние приложения управляется сложно. Постоянное отслеживание того, что было обновлено, почему и когда может стать кошмаром, особенно в крупных приложениях. В мире Angular самым популярным решением является ngrx/store, который был вдохновлен известной моделью Redux. Лично я большой поклонник Redux и много работал с ngrx; Однако, как разработчик, так и консультант, я также сталкивался с необходимостью другого решения.
Рождение Akita
Здесь, в Datorama, мы экспериментировали с несколькими инструментами, включая mobx и ngrx, и решили, что они не подходят. Вот почему я решил создать другое управление состоянием, которое лучше всего соответствует нашим потребностям. Это также может служить решением для разработчиков, которые не чувствуют себя связанными с концепциями Redux или затрудняются с ними.
Akita уже давно используется Datorama, и мы решили, что теперь она достаточно зрелая, чтобы ее можно было опубликовать как открытый источник. Почти вся наша база кода была перенесена в Akita с mobx и ngrx. Команда довольна тем, что нашла Akita как простую, так и эффективную, и мы видели улучшения как во время доставки, так и в производительности.
Стоит отметить, что Datorama — это корпоративная компания со сложным управлением состоянием, множеством сложных взаимосвязанных объектов, фильтров и т. д.
Что такое Akita?
Akita — это шаблон управления состоянием, построенный на основе RxJS, который берет идею нескольких хранилищ данных от Flux и неизменных обновлений от Redux вместе с концепцией потоковых данных для создания модели Observable Data Stores.
Akita поощряет простоту. Это избавляет вас от необходимости создавать шаблонный код и предлагает мощные инструменты с умеренной кривой обучения, подходящие как для опытных, так и для неопытных разработчиков.
Akita основана на объектно-ориентированных принципах дизайна вместо функционального программирования, поэтому разработчики с опытом ООП должны чувствовать себя как дома. Его укоренившаяся структура предоставляет вашей команде фиксированный шаблон, от которого нельзя отклониться.
Архитектура Akita
Принципы высокого уровня
Store — это единственный объект, который содержит состояние магазина и служит «единственным источником правды».
Единственный способ изменить состояние — это вызвать setState() или один из методов обновления на его основе.
Компонент НЕ должен получать данные из магазина напрямую, но вместо этого используйте запрос.
Асинхронная логика и вызовы обновлений должны быть инкапсулированы в службы и службы данных.
Начнем с изучения основных понятий Akita. Хотя Akita поддерживает как обычный магазин, так и хранилище сущностей, мы сосредоточимся на функции хранения сущностей, потому что по большей части это будет тот, который вам понадобится в ваших приложениях.
Основные понятия
Модель
Модель представляет собой представление объекта. Давайте возьмем, например, todo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { ID } from '@datorama/akita'; export type Todo = { id: ID; title: string; completed: boolean; }; export function createTodo({ id, title }: Partial<Todo>) { return { id, title, completed: false } as Todo; } |
Akita рекомендовал создать как тип, так и заводскую функцию, отвечающую за создание объекта.
Магазин сущностей
Магазин представляет собой единый объект, который содержит состояние магазина и служит «единственным источником правды». Вы можете представить хранилище объектов как таблицу в базе данных, где каждая таблица представляет собой плоскую совокупность объектов.
EntityStore от EntityStore упрощает процесс, предоставляя вам все необходимое для управления. Давайте посмотрим, как мы можем использовать его для создания таблицы todos, то есть EntityStore управляющего объектом Todo:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { Todo } from './todo.model'; import { EntityState, EntityStore } from '@datorama/akita'; export interface State extends EntityState<Todo> {} @Injectable({ providedIn: 'root' }) export class TodosStore extends EntityStore<State, Todo> { constructor() { super(initialState); } } |
Во-первых, нам нужно определить интерфейс магазина. В нашем случае мы можем заниматься расширением EntityState от Akita, предоставляя ему тип EntityState Entity. Если вам интересно, EntityState имеет следующую подпись:
1 2 3 4 5 6 |
export interface EntityState<T> { entities: HashMap<T>; ids: ID[]; loading: boolean; error: any; } |
Используя эту модель, вы получите от Akita множество встроенных функций, таких как операции crud на объектах, управление ошибками и т. д. Например, вот как мы можем добавить новое todo:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Component({ ... }) export class TodosPageComponent implements OnInit { constructor(private todosService: TodosService) {} add(input: HTMLInputElement) { this.todosService.add(input.value); input.value = ''; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Injectable({ providedIn: 'root' }) export class TodosService { constructor(private todosStore: TodosStore) {} add(title: string) { const todo = createTodo({ id: Math.random(), title }); this.todosStore.add(todo); } } |
Или пример того, как мы можем обновить todo:
1 2 3 4 5 6 7 8 9 10 11 12 |
@Injectable({ providedIn: 'root' }) export class TodosService { constructor(private todosStore: TodosStore) {} complete({ id, completed }: Todo) { this.todosStore.update(id, { completed }); } } |
1 2 3 4 5 6 7 8 9 |
@Component({ ... }) export class TodosPageComponent{ complete(todo: Todo) { this.todosService.complete(todo); } } |
Для полного API проверьте документацию.
Entity Query
Query — это класс, предлагающий функциональность, отвечающую за запрос в хранилище. Вы можете считать запрос похожим на запросы к базе данных. Его функция-конструктор получает в качестве параметров свой собственный магазин и, возможно, другие классы запросов. Запросы могут общаться с другими запросами, объединять объекты из разных магазинов и т. д.
Давайте посмотрим, как мы можем использовать его для создания запроса todos:
1 2 3 4 5 6 7 8 9 10 |
import { QueryEntity } from '@datorama/akita'; @Injectable({ providedIn: 'root' }) export class TodosQuery extends QueryEntity<State, Todo> { constructor(protected store: TodosStore) { super(store); } } |
Здесь вы получите от Akita множество встроенных функций, включая такие методы, как selectAll(), selectEntity(), selectMany() и т. д. Например, вот как мы можем оперативно запросить список todos:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Component({ template: '<todo *ngFor="let todo of todos$ | async" [todo]="todo"><todo>' }) export class TodosPageComponent implements OnInit { todos$: Observable<Todo[]>; constructor(private todosQuery: TodosQuery) {} ngOnInit() { this.todos$ = this.todosQuery.selectAll(); } } |
Или пример того, как мы можем запросить полный статус первого todo:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Component({ ... }) export class TodosPageComponent implements OnInit { todoCompleted$: Observable<boolean>; constructor(private todosQuery: TodosQuery) {} ngOnInit() { this.todoCompleted$ = this.todosQuery.selectEntity(1, todo => todo.complete); } } |
Опять же, для полного API проверьте документацию.
Здесь представлено полное рабочее приложение todos:
В следующем посте будет показан пример корзины покупок с помощью Akita, который продемонстрирует реальную функциональность Akita.
Автор: Netanel Basal
Источник: //netbasal.com/
Редакция: Команда webformyself.