NgRx — это библиотека для управления состоянием в приложениях, разработанных с использованием фреймворка Angular. Состояние — это глобальное представление данных в приложении, и его управление может быть сложной задачей. NgRx предоставляет паттерны и инструменты, которые помогают сделать это гораздо проще и эффективнее.
В основе NgRx лежит концепция однонаправленного потока данных. Сначала состояние инициализируется, а затем может быть изменено только с помощью действий, которые представляют собой простые объекты с определенными свойствами. Действия отправляются в централизованный хранилище, а затем проходят через редукторы, которые объединяют текущее состояние с действием и возвращают новое состояние.
NgRx также предоставляет возможность использования селекторов для получения данных из состояния. Селекторы — это функции, которые принимают текущее состояние и возвращают выбранные данные. Они позволяют избежать прямого доступа к состоянию и позволяют управлять данными более гибко и эффективно.
В этом подробном руководстве мы рассмотрим основы работы с NgRx, начиная с настройки хранилища и создания действий и редукторов, а затем перейдем к использованию селекторов для получения данных и диспетчеризации действий. Вы также узнаете о различных возможностях и паттернах, которые предоставляет NgRx, и научитесь их применять в своих приложениях.
Основные понятия ngrx
Основными понятиями, с которыми придется столкнуться при работе с ngrx, являются:
- Actions: действия представляют собой объекты, которые описывают события, происходящие в приложении. Они передают информацию о действии и используются для вызова соответствующих редукторов.
- Reducers: редукторы — это функции, которые принимают текущее состояние и действие, и возвращают новое состояние приложения. Они отвечают за обновление состояния, основываясь на переданных действиях.
- Store: хранилище — это объект, который содержит всю информацию о состоянии приложения. Он предоставляет интерфейс для чтения и записи данных приложения.
- Selectors: селекторы — это функции, которые используются для извлечения определенных кусков данных из состояния приложения. Они позволяют получить только нужные данные, минимизируя количество кода и упрощая разработку.
- Effects: эффекты — это функции, которые позволяют обрабатывать побочные эффекты, такие как асинхронные вызовы к API или изменение маршрута. Они могут быть использованы для вызова действий, обновления состояния приложения и других побочных эффектов.
Эти основные понятия формируют основу для работы с ngrx и обеспечивают единообразие, предсказуемость и управляемость состояния в Angular приложениях.
Преимущества использования ngrx
Использование библиотеки ngrx для управления состоянием в Angular-приложениях имеет множество преимуществ. Вот некоторые из них:
1. | Централизованное хранение состояния. | Состояние приложения хранится в едином объекте, который можно легко отслеживать и изменять с помощью однонаправленного потока данных. |
2. | Упрощение логики приложения. | Благодаря использованию глобального хранилища состояния, логика приложения становится проще и более предсказуемой. Компоненты становятся малозависимыми и могут сосредоточиться на отображении данных. |
3. | Легкая отладка и поддержка. | С использованием инструментов разработчика можно легко отслеживать изменения состояния и понимать, какие действия вызывают эти изменения. Это существенно упрощает отладку и поддержку приложения. |
4. | Возможность переиспользования состояния. | Хранение состояния в отдельном хранилище позволяет переиспользовать его между различными компонентами и даже между различными Angular-приложениями. |
5. | Улучшение производительности. | Благодаря использованию иммутабельности и упрощению логики обновления состояния, ngrx способствует более эффективной работе с данными и повышает производительность приложения. |
Использование ngrx для управления состоянием в Angular-приложениях позволяет создавать более масштабируемые, надежные и легко поддерживаемые приложения.
Шаблон проектирования в ngrx
Состояние представляет собой единственный объект, который содержит всю информацию о приложении. В NgRx состояние хранится в структурированном виде, подобно базе данных. Каждый кусочек данных должен быть легко доступным и изменяемым.
Действия представляют собой события, которые указывают, что произошло изменение состояния. Они описывают, какую операцию нужно выполнить с состоянием. Действия в NgRx являются неизменяемыми и имеют тип и данные, связанные с соответствующим действием.
Редукторы являются чистыми функциями, которые обрабатывают действия и изменяют состояние приложения. Они принимают текущее состояние и действие, а затем возвращают новое состояние. Редукторы в NgRx должны быть детерминированными, то есть результат выполнения редуктора должен быть одинаковым при одинаковых входных данных.
Шаблон проектирования в NgRx гарантирует, что изменения состояния происходят только через действия и редукторы. Действия служат механизмом чтения, а редукторы — механизмом записи состояния. Это обеспечивает предсказуемость и контроль в приложении, позволяя отслеживать, что происходит внутри, и легко отменять или повторять изменения.
Шаблон проектирования в NgRx также позволяет использовать селекторы для получения данных из состояния. Селекторы — это функции, которые принимают состояние в качестве аргумента и возвращают определенную информацию. Они позволяют избежать ненужных перерисовок и упрощают получение данных из состояния.
Использование шаблона проектирования в NgRx позволяет создавать масштабируемые и легко тестируемые приложения. Он помогает упростить управление состоянием и обеспечивает однозначность взаимодействия с состоянием в Angular-приложении.
Централизованное хранение состояния
Хранилище — это объект, который содержит всю необходимую информацию о состоянии приложения. Оно представляет собой некоторую коллекцию данных, которые можно получать и изменять через определенные правила и механизмы.
Основная идея централизованного хранения состоит в том, чтобы иметь одну единственную истину (состояние), к которой можно обратиться из любого компонента приложения. Это позволяет упростить код и сделать его более предсказуемым.
Для работы с централизованным хранилищем ngrx предлагает использовать следующие концепции:
Концепция | Описание |
---|---|
Actions | Действия, которые могут происходить в системе. Они представляют собой простые объекты, содержащие информацию о произошедшем событии. |
Reducers | Функции, которые обрабатывают действия и обновляют состояние хранилища. Они принимают текущее состояние и действие, а затем возвращают новое состояние. |
Selectors | Функции, которые позволяют получать нужные данные из состояния хранилища. Они предоставляют удобный способ извлечения и трансформации данных для компонентов приложения. |
С помощью этих концепций можно организовать связь между компонентами приложения и состоянием хранилища. Компоненты могут отправлять действия, которые обрабатываются редьюсерами, а затем получать обновленное состояние через селекторы.
Централизованное хранение состояния является одним из ключевых преимуществ использования ngrx. Это позволяет легко отслеживать и контролировать изменения в приложении, а также упрощает разработку и поддержку кода.
Действия и редукторы
Действия в ngrx представляются в виде классов, обычно называемых «действием», которые реализуют интерфейс Action
. Этот интерфейс определяет обязательное свойство type
— строку, которая описывает тип действия. Кроме того, действия могут содержать дополнительные свойства, которые описывают данные, передаваемые с действием.
Редукторы в ngrx — это функции, которые принимают два параметра: текущее состояние и действие. Они должны быть чистыми функциями, то есть не иметь побочных эффектов и всегда возвращать новый объект состояния, а не изменять его напрямую. Редукторы обычно используют оператор switch
для проверки типа действия и определения, какое изменение состояния необходимо выполнить.
Все действия, определенные для приложения, должны быть перечислены в структуре состояния, которая называется «стор». Рекомендуется использовать отдельный файл для определения действий. Затем действия экспортируются и могут быть импортированы в редукторы и компоненты приложения.
Пример использования действий и редукторов:
Действие | Редуктор |
---|---|
LoadDataAction | loadDataReducer |
AddItemAction | addItemReducer |
DeleteItemAction | deleteItemReducer |
В приведенном примере LoadDataAction
, AddItemAction
и DeleteItemAction
— это классы действий, которые определены в отдельных файлах. При выполнении действий соответствующие редукторы будут вызваны с текущим состоянием и переданным действием. Редукторы могут изменять состояние приложения и возвращать новое состояние.
Использование действий и редукторов позволяет создавать предсказуемое и масштабируемое управление состоянием в приложении ngrx. Действия определяют, какие изменения должны быть выполнены, а редукторы обрабатывают эти изменения и возвращают новое состояние. Это позволяет создавать составные действия и иметь полный контроль над изменениями в приложении.
Эффекты и селекторы
Эффекты представляют собой функции, которые выполняют некоторое действие при наступлении определенного события. Например, эффект может отправлять HTTP-запрос для получения данных с сервера или обновлять состояние приложения в ответ на определенные действия пользователя.
Селекторы, в свою очередь, используются для выборки данных из глобального состояния приложения. Они позволяют организовать доступ к данным из разных компонентов, необходимых для их отображения или использования.
При использовании эффектов и селекторов важно следовать принципу иммутабельности данных. Это означает, что состояние приложения не изменяется напрямую, а только через действия и редукторы.
Например, эффект может быть настроен на отслеживание события нажатия кнопки и запуск действия, которое в свою очередь вызывает редуктор и обновляет состояние приложения. Затем селектор может быть использован для выборки обновленных данных из состояния и использования их в компонентах.
Эффекты | Селекторы |
---|---|
Отвечают за выполнение определенных действий в ответ на события | Позволяют выбирать данные из состояния приложения |
Могут выполнять асинхронные операции, такие как получение данных с сервера или отправка запросов | Позволяют разделить логику доступа к данным от компонентов |
Могут использоваться для управления другими сайд-эффектами, такими как логирование или аналитика | Позволяют легко изменять формат или структуру данных, возвращаемых компонентам |
Использование эффектов и селекторов позволяет создавать более эффективные и поддерживаемые приложения. Они помогают разделить логику обработки событий и доступа к данным от компонентов, что делает код более читабельным и гибким.
При разработке приложения с использованием ngrx рекомендуется активно использовать эффекты и селекторы для управления состоянием и доступа к данным.
Пример применения ngrx в приложении
Для наглядной иллюстрации работы библиотеки ngrx, рассмотрим пример применения ее основных концепций в приложении для управления списком задач.
1. Создание стора:
Сначала мы создаем стор, который будет хранить состояние нашего приложения. В этом примере, стор будет содержать массив задач и текущий выбранный фильтр.
store.module.ts
import { NgModule } from '@angular/core';import { StoreModule } from '@ngrx/store';import { reducers } from './reducers';@NgModule({imports: [StoreModule.forRoot(reducers)]})export class AppStoreModule {}
2. Создание редьюсеров:
Затем мы создаем редьюсеры, которые будут обрабатывать действия и обновлять состояние стора. В этом примере, у нас есть два редьюсера: один для обработки действий связанных с задачами, и другой для обработки действия связанного с выбранным фильтром.
reducers.ts
import { ActionReducerMap } from '@ngrx/store';import { taskReducer } from './task.reducer';import { filterReducer } from './filter.reducer';export interface AppState {tasks: Array<string>;filter: string;}export const reducers: ActionReducerMap<AppState> = {tasks: taskReducer,filter: filterReducer};
3. Создание действий:
Мы также создаем действия, которые будут отправлены в редьюсеры для обработки. В этом примере, у нас есть действия для добавления новой задачи, удаления задачи и изменения выбранного фильтра.
actions.ts
import { createAction, props } from '@ngrx/store';export const addTask = createAction('[Task] Add Task',props<{ task: string }>());export const deleteTask = createAction('[Task] Delete Task',props<{ task: string }>());export const changeFilter = createAction('[Filter] Change Filter',props<{ filter: string }>());
4. Создание компонента:
Затем мы создаем компонент, который будет использовать стор для получения и отображения задач и текущего выбранного фильтра. Компонент также должен быть подписан на изменения состояния стора, чтобы обновлять отображение при изменении состояния.
task-list.component.ts
import { Component, OnInit } from '@angular/core';import { Store, select } from '@ngrx/store';import { Observable } from 'rxjs';import { AppState } from './reducers';import { selectTasks, selectFilter } from './selectors';@Component({selector: 'app-task-list',template: `<ul><li *ngFor="let task of tasks$ | async">{{ task }}</li></ul><p>Selected Filter: async }</p>`})export class TaskListComponent implements OnInit {tasks$: Observable<Array<string>>;filter$: Observable<string>;constructor(private store: Store<AppState>) {}ngOnInit() {this.tasks$ = this.store.pipe(select(selectTasks));this.filter$ = this.store.pipe(select(selectFilter));}}
5. Использование действий и редьюсеров:
Компонент также должен быть способен отправлять действия в редьюсеры для выполнения изменений состояния. В этом примере, мы используем действия для добавления новой задачи, удаления задачи и изменения выбранного фильтра.
task-list.component.ts
import { Component } from '@angular/core';import { Store } from '@ngrx/store';import { addTask, deleteTask, changeFilter } from './actions';@Component({selector: 'app-task-list',template: `<input [(ngModel)]="newTask" placeholder="New Task" /><button (click)="onAddTask()">Add Task</button><button (click)="onDeleteTask(task)">Delete Task</button><button (click)="onChangeFilter(filter)">Change Filter</button>`})export class TaskListComponent {newTask = '';taskToDelete = '';filterToChange = '';constructor(private store: Store<AppState>) {}onAddTask() {this.store.dispatch(addTask({ task: this.newTask }));this.newTask = '';}onDeleteTask(task: string) {this.store.dispatch(deleteTask({ task: task }));}onChangeFilter(filter: string) {this.store.dispatch(changeFilter({ filter: filter }));}}
Это только простой пример использования ngrx в приложении. Библиотека ngrx предоставляет мощные инструменты для управления состоянием в Angular приложениях, и может быть использована для более сложных случаев, включая асинхронное обновление состояния, эффекты и другие продвинутые функции.
Обратите внимание, что данный пример был упрощен для наглядности и не содержит полного кода приложения.
Настройка ngrx в проекте
Для использования библиотеки ngrx в вашем проекте необходимо выполнить несколько шагов настройки:
1. Установите необходимые зависимости:
npm install @ngrx/store
npm install @ngrx/effects
npm install @ngrx/entity
npm install @ngrx/store-devtools
2. Создайте папку «state» в корне вашего проекта. В этой папке будут храниться все файлы, связанные с состоянием приложения.
3. Создайте файл «app.reducer.ts» в папке «state». В этом файле будут объявлены и скомбинированы все редюсеры вашего приложения. Например:
import { combineReducers } from '@ngrx/store';
import { reducer as counterReducer } from './counter/counter.reducer';
import { reducer as todosReducer } from './todos/todos.reducer';
import { reducer as userReducer } from './user/user.reducer';
export const rootReducer = combineReducers({
counter: counterReducer,
todos: todosReducer,
user: userReducer
});
4. Создайте файлы для каждого редюсера, например «counter.reducer.ts», «todos.reducer.ts», «user.reducer.ts», в папке «state». Реализуйте логику каждого редюсера в соответствии с требованиями вашего приложения.
5. Создайте файл «app.actions.ts» в папке «state». В этом файле будут объявлены все действия, которые могут производиться в вашем приложении. Например:
import { createAction, props } from '@ngrx/store';
export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
export const addTodo = createAction('[Todos] Add Todo', props<{ title: string }>());
export const removeTodo = createAction('[Todos] Remove Todo', props<{ id: string }>());
export const setUser = createAction('[User] Set User', props<{ user: User }>());
6. Создайте файл «app.effects.ts» в папке «state». В этом файле будут объявлены все эффекты, которые могут быть связаны с вашими действиями. Например:
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { map, mergeMap } from 'rxjs/operators';
import { UserService } from '../services/user.service';
import { setUser } from './app.actions';
@Injectable()
export class AppEffects {
setUser$ = createEffect(() => {
return this.actions$.pipe(
ofType('[User] Load User'),
mergeMap(() =>
this.userService.getUser().pipe(
map(user => setUser({ user }))
)
});
}
7. Импортируйте и добавьте в список импортов все редюсеры, эффекты и действия в файле «app.module.ts»:
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { rootReducer } from './state/app.reducer';
import { AppEffects } from './state/app.effects';
import { appReducer } from './state/app.reducer';
@NgModule({
imports: [
StoreModule.forRoot(appReducer),
EffectsModule.forRoot([AppEffects])
]
})
export class AppModule { }
Теперь вы готовы использовать ngrx в вашем проекте. Можете создавать селекторы, диспатчить действия и обрабатывать их с помощью редюсеров и эффектов.