Хабр, привет!

В этой статье будет описана одна очень полезная вещь, способная упростить жизнь разработчику, которому приходилось (или приходится) работать с библиотекой NgRx. Сейчас это достаточно популярно и востребованно. В частности, я покажу, как избавится от привычного описания selectors, сокращения кода по части reducers и, уверен, результат удивит.

Что такое NgRx feature?

Начнем с общих понятий и поговорим о том, как всё устроено.

Существует три основных строительных блока глобального управления состоянием в вашем проекте при использовании @ngrx/store:

Действия – actions;
Редюсеры – reducers;
Селекторы – selectors.

Для вынесения определенной логики из компонента, обработки побочных эффектов и т.д. существуют еще и effects. Но о них сейчас речи идти не будет.

Для определенного состояния объекта мы создаем reducer. Он обрабатывает переходы состояния на основе отправленных actions. При помощи selectors у нас есть возможность отслеживать изменения фрагментов этого определенного состояния, а также их получать.

Более подробно углубляться в эту тему не буду, перейдем к описанию более сложных вещей, понимание которых как раз-таки строится на полном освоении базовых понятий.

Помимо всех вышеописанных действий, нам нужно определить имя фичи, необходимое для регистрации reducers в хранилище.

Мы можем рассматривать NgRx Feature как группировку её имени, reducer и selector для конкретного состояния. То есть это полностью укомплектованная всем функционалом часть хранилища, обособленная от таких же частей, которая отвечает за хранение необходимых переменных, их обработку, а также всю сопутствующую при этом логику.

В проектах любой сложности возникает такая ситуация, когда определенный фрагмент состояния содержит какое-то количество полей, не требующих вычислений и не участвующих в них. То есть они добавляются в начальное состояние, при помощи actions изменяются и используются базовыми selectors в компонентах или сервисах. Selectors этих полей просто берут их из фрагмента store, за который отвечает их featureSelector и возвращают нам.

Все эти селекторы необходимо вручную прописывать, делают они одно и тоже, различие лишь в том, для какого поля они описаны.

Создание функциональных элементов по старинке

Создание файла редюсеров

Итак, создадим таким образом редюсер для фрагмента стора, отвечающего за создание заявок.

Воспользуемся createReducer функцией из пакета @ngrx/store.

На самом деле для кого-то может и использование этой функции уже показаться довольно прогрессивным, но мы пойдем дальше.

import { createReducer, on } from '@ngrx/store';
import { ApplicationToSendActions } from "./applicationToSend.actions";
import { Applications } from "../../../../models/applications.model";

export namespace applicationToSendReducer {

export const applicationToSendFeatureKey = 'applicationToSend';

export interface ApplicationToSendState {
  applicationView: Applications;
  applicationFirstPart: any;
  applicationSecondPart: any;
  applicationThirdPart: any;
}

export const initialState: ApplicationToSendState = {
  applicationView: null,
  applicationFirstPart: null,
  applicationSecondPart: null,
  applicationThirdPart: null
}

export const reducer = createReducer(
  initialState,
  on( ApplicationToSendActions.addFirstPartToApplication,( state, { applicationFirstPart } ) => ( { ...state, applicationFirstPart } ) ),
  on( ApplicationToSendActions.addSecondPartToApplication, ( state, { applicationSecondPart } ) => ( { ...state, applicationSecondPart } ) ),
  on( ApplicationToSendActions.addThirdPartToApplication, ( state, { applicationThirdPart } ) => ( { ...state, applicationThirdPart } ) ),
  on( ApplicationToSendActions.addApplicationViewObject, ( state, { applicationView } ) => ( { ...state, applicationView } ) )
)}

Чтобы зарегистрировать этот редуктор в хранилище NgRx, мы используем StoreModule.forFeature метод.

store.module.ts

StoreModule.forFeature( applicationToSendReducer.applicationToSendFeatureKey, applicationToSendReducer.reducer )

Куда передаем название фичи, а также сам редюсер.

Создание файла селекторов

Чтобы выбрать поля состояния создадим селектор объектов, дочерние селекторы, а также селектор модели представления:

selectors.ts

import { createFeatureSelector, createSelector } from '@ngrx/store';
import { applicationToSendReducer } from "./applicationToSend.reducer";

export namespace applicationToSendSelectors {

  export const applicationToSendState =
  createFeatureSelector<applicationToSendReducer.ApplicationToSendState>
  ( applicationToSendReducer.applicationToSendFeatureKey );
  
  export const applicationFirstPartSelector = createSelector( applicationToSendState, ( { applicationFirstPart } ) => applicationFirstPart )
  export const applicationSecondPartSelector = createSelector( applicationToSendState, ( { applicationSecondPart } ) => applicationSecondPart )
  export const applicationThirdPartSelector = createSelector( applicationToSendState, ( { applicationThirdPart } ) => applicationThirdPart )
  export const applicationViewSelector = createSelector( applicationToSendState, ( { applicationView } ) => applicationView )
}

Как видно несколько базовых селекторов по типу взял из хранилища одно поле, и отдал в компонент или сервис. Выглядит не громоздко, однако их всего 4 штуки, а как бы это выглядело с большим количеством селекторов. Огромная куча одинакового повторяющегося кода, отличающегося только именами полей, которые там пробрасываются. К тому же каждый разработчик в своем проекте придерживается своей политики наименований, и не всегда по названию селектора можно определить за что он отвечает, и вообще селектор ли это.

Использование Feature Creator

Давайте теперь посмотрим, как добиться того же результата, но уже без вышеописанных проблем.

Для начала проведем небольшой рефаткоринг редюсера.

Изменения в файле редюсеров

reducer.ts

import { createFeature, createReducer, on } from '@ngrx/store';
import { ApplicationToSendActions } from "./applicationToSend.actions";
import { Applications } from "../../../../models/applications.model";

export interface ApplicationToSendState {
  applicationView: Applications;
  applicationFirstPart: any;
  applicationSecondPart: any;
  applicationThirdPart: any;
}

export const initialState: ApplicationToSendState = {
  applicationView: null,
  applicationFirstPart: null,
  applicationSecondPart: null,
  applicationThirdPart: null
}

export const applicationToSendFeature = createFeature({
  name: "applicationToSend",
  reducer: createReducer(
  initialState,
  on( ApplicationToSendActions.addFirstPartToApplication, ( state, { applicationFirstPart } ) => ( { ...state, applicationFirstPart } ) ),
  on( ApplicationToSendActions.addSecondPartToApplication, ( state, { applicationSecondPart } ) => ( { ...state, applicationSecondPart } ) ),
  on( ApplicationToSendActions.addThirdPartToApplication, ( state, { applicationThirdPart } ) => ( { ...state, applicationThirdPart } ) ),
  on( ApplicationToSendActions.addApplicationViewObject, ( state, { applicationView } ) => ( { ...state, applicationView } ) )
  )
})

Уберем лишнюю обертку для редюсера, а также поле applicationToSendFeatureKey.

Вместо этого используем функцию createFeature, принимающую в себя обьект с полями name – имя фичи, фрагмента хранилища, за который отвечает редюсер, а так же поле reducer, где описаны редюсеры для определенных actions.

Уже сейчас видно, что код стал более лаконичным, все основные сущности помещены в одно место и кроме интерфейса фрагмента хранилища, его начального состояния, и самого обьекта редюсера в файле ничего нет.

Регистрация фичи в модуле

Теперь для регистрации фичи достаточно в модуле передать один обьект, который мы создали.

store.module.ts

StoreModule.forFeature( applicationToSendFeature )

Изменения в файле селекторов

А теперь самое главное: простота и удобство, которое вносит createFeature больше всего заметно в файле селекторов.

Вспомним, что до использования этой функции селекторы представляли собой нагромождение повторяющегося одинакового кода.

А теперь файл селекторов выглядит так:

selectors.ts

import {applicationToSendFeature} from "./applicationToSend.reducer";

const {
  selectApplicationToSendState,
  selectApplicationThirdPart,
  selectApplicationSecondPart,
  selectApplicationFirstPart,
  selectApplicationView
} = applicationToSendFeature

export const applicationToSendSelectors = {
  selectApplicationThirdPart,
  selectApplicationSecondPart,
  selectApplicationFirstPart,
  selectApplicationView,
  selectApplicationToSendState
}

Помимо сокращения импортов, существенно преобразился код.

Итак, по порядку:

  • Функция createFeature убирает необходимость в такой сущности как featureSelector.

  • Теперь не нужно прописывать вручную селектор для каждого поля хранилища, ведь они создаются автоматически.

  • К именам полей хранилища добавляются приставки select, а к селектору фичи помимо приставки добавляется и суффикс State.

  • Так, имя селектора фичи у нас это selectApplicationToSendState, а имена дочерних селекторов: selectApplicationThirdPart,selectApplicationSecondPart, selectApplicationFirstPart.

  • Ничего писать больше не нужно, достаточно обьявить все селекторы в файле, и затем экспортировать необходимые для дальнейшего использования.

  • Более того, из этих селекторов можно создавать свои, более сложные селекторы.

  • Теперь достаточно заменить имена селекторов в компонентах, где они использовались на новые. Они доступны точно таким же образом как и в прошлом варианте их использования.

Заключение

Создатели функций сокращают количество повторяющегося кода в файлах селекторов, используя возможности шаблонных литеральных типов, представленных в TypeScript версии 4.1.1. Это может значительно улучшить ваш код, особенно при больших состояниях функций.

Комментарии (0)