Автор оригинала: José Proença.
Фото от Государственная библиотека Queensland: цифровая коллекция изображений
Вступление
Это вторая часть ряд статей о примере исследования для веб-приложения, реализованного с использованием Redux и Угловой Отказ Проект следует за архитектурой, изображенной на Предыдущая статья , но читая эту статью до этого не нужна.
Для удобства ссылки здесь есть статьи в серии:
- Тематическое исследование PT.1: планирование бизнес-логики с использованием Redux
- Исследование корпуса PT.2: Реализация Redux на угловой (эта статья)
- Исследование случаев PT.3: тестирование эффектов redux на угловой (для публикации)
Это веб-приложение этого проекта является онлайн-тестом (или викторином), который, хотя функционал, упрощенный, так как он служит только в соответствии с этим тематическим исследованием. Проект завершен на Мой репозиторий И эти статьи сосредоточены на реализации бизнес-логики, используя Redux на угловом веб-приложении.
Снимок веб-приложения реализован для этой серии статей. Исходный код включен мой github. Репозиторий.
На этой статье я объясню, как логика проекта структурирована и реализована. Предполагается, что некоторые знания Redux предполагают и не ожидают никаких угловых деталей здесь.
Модульность
Я решил изолировать функциональность теста/экзамена в угловом модуле Exammodule
Отказ Это более чистое проще перейти к другому проекту. Кроме того, этот модуль ленивый загружен для проверки его полной модульности. Быть ленивым загруженным, услуги становятся выделенными из основного приложения, что желательно в этом случае, поскольку этот модуль не предназначен, чтобы быть вызванным из-за пределов модуля.
В папке « экзамен » У нас есть три лучших файла:
- Exam.module.ts : Объявляет все компоненты пользовательского интерфейса и делают все доступные услуги. Нормальный угловой модуль вещи.
- Экзамен-маршрут. Module.ts : Определяет URL, которые загружают дерево разных страниц: начать, вопрос и результат. Этот проект использует « Router-Store-Ser », который обеспечивает сериализацию для маршрутизатора в состоянии redux. Не важно для этой статьи, так что увидеть больше на этом репозитории.
- Модуль-Config.ts : Этот файл – единственное место, где этот модуль знает о его внешней стороне. Помимо этого файла нет зависимостей для других модулей в приложении.
Логика имеет свой собственный модуль в файле « Logic.module.ts », где хранилище Redux настроен и прилагается, и эффекты также настроены:
@NgModule({ imports: [ StoreModule.forFeature(featureName, reducersMap), EffectsModule.forFeature([ RouterOutEffects, RouterResultEffects, RouterStartEffects, RouterQuestionCurrentEffects, ExamStartEffects, ExamEndEffects, ]), ], providers: [ { provide: MODULE_STORE_TOKEN, useFactory: featureSelector, deps: [Store], }, ], }) export class LogicModule {}
Обратите внимание на поставщик, использующий пользовательский токен Module_store_token
Отказ Весь код модуля экзамена доступа к магазину Redux, впрыскивая этот токен, таким образом, у него есть собственное изолированное состояние, предоставляемое Осекретник
:
export function featureSelector(store$: Store): Store { return store$.select(createFeatureSelector(featureName)); }
Давайте немного пойти на это, проверяя все состояние redux, когда приложение запускается. Давайте проверим это Devtools расширение Снимок:
Верхний уровень включает в себя только два свойства:
- RoutErrectucer Отказ Создано приложением (за пределами модуля экзамена) для хранения состояния навигации.
- экзамен Отказ Верхний уровень для экзаменационного модуля состояния.
Название «экзамен» определяется в файле « Module-Config.ts » (как видно выше) и подается в Storemodule.Forfeature (featurename, редукторы)
на модуле импорт выше. Впрыскивая магазин с помощью пользовательского токена Module_store_token
Получите объект на этом «экзамене» главной собственности. Этот объект имеет два свойства: «экзамен» и «вопросы». Эти два свойства определяются Редукторы пометки
:
export const reducersMap: ActionReducerMap= { exam: examReducer, questions: questionsReducer, };
Actionreducermap
хелпер из @ Ngrx/Store объединить редукторы, каждый с его ломтиком состояния.
Состав
Следует на снимке папки проекта «экзамен» расширена, чтобы показать логику, связанные с файлами и папками:
Папка «Модели» удерживает классы и интерфейсы, которые делают нашу модель бизнес-данных. Папка «Логика» включает в себя файлы для бизнес-логики и давайте проанализируем каждый.
Состояние
Файл «State.ts» включает в себя верхнюю структуру для верхнего свойства состояния redux “, как видно выше.
import { State as ExamState } from './exam.state'; import { State as QuestionsState } from './questions.state'; export interface State { exam: ExamState; questions: QuestionsState; } /** * This token is used for providing the store fragment that corresponds to this ngModule. * So, on this module asking the injector for Store should always ask for this token. */ export const MODULE_STORE_TOKEN = new InjectionToken('ModuleStore');
Токен Module_store_token
обсуждался выше и определяется здесь. Это может иметь свой собственный файл, но на данный момент, который кажется накладным расходом. Обратите внимание на Импорт
заявления. Каждая государственная часть определяет себя как Государство
Оставляя код, который импортирует его, чтобы переименовать его сопоставить свойство, которое будет использоваться для хранения этого состояния.
Файл “exam.state.ts” определяет Экзамен
упоминается в «штате»:
export enum ExamStatus { OFF, READY, RUNNING, TIME_ENDED, ENDED } export interface State { data: AsyncDataSer; resultScore: AsyncDataSer; timeLeft: number; // seconds status: ExamStatus; } export const initialState: State = { data: null, resultScore: null, timeLeft: 0, status: ExamStatus.OFF, };
Обратите внимание, что исходное состояние, необходимое в инициализации Redux, также определено здесь. Ускорить возможные изменения в Государство
интерфейс.
Действия
Действия должны быть только объектом с «типом» имуществом, но объявляя их в качестве классов, позволяют проверять тип компиляции.
/** * Emitted for changing the status of the exam. */ export class ExamStatusAction implements Action { public readonly type = ExamStatusAction.type; public static type = 'EXAM_STATUS'; constructor( public payload: { status: ExamStatus }, ) { } }
Статический Тип
Свойство будет использоваться для проверки, является ли существующее действие экземпляром этого типа действия. Обратите внимание, что действия могут прийти из сериализации, где оператор «экземпляр» не будет работать.
Обратите внимание, что полезная нагрузка в случае выше, кажется накладной, так как она может быть типа ЭКСПЕРТУС
напрямую. Я всегда использую объект для полезной нагрузки. Эта опция оправдана:
- Наличие имени свойства, добавляет больше семантиков на полезную нагрузку действия
- немного легче добавлять свойства в будущем
Редукторы
Редукторы внедрения в значительной степени следует стандартной практике. Позвольте обратиться к отрыву ” exam.reducer.ts “:
export function reducer(state: State = initialState, action: Action): State { switch (action.type) { case examActions.ExamStatusAction.type: return { ...state, status: (action as examActions.ExamStatusAction).payload.status }; case examActions.ExamDataAction.type: { let timeLeft = 0; const adata: AsyncDataSer = (action as examActions.ExamDataAction).payload.data; if (AsyncDataSer.hasData(adata, false)) timeLeft = adata.data.duration; return { ...state, timeLeft, data: (action as examActions.ExamDataAction).payload.data }; } } return state; }
Обратите внимание, что для действий Экзаменация зависимости
и кроме того, находящееся состояние, есть логика, указанная в планировании для настройки Timeleft
Отказ
Другие редукторы похожи и можно увидеть на Репозиторий Отказ
Эффекты
Как мы видели в прежней статье, эффекты реализуют большую часть бизнес-логики нашего приложения. Процедура заключается в том, чтобы обнаружить действие, которое вызывает эффект и испускает новые действия в видах ответа. Этот код эффектов может совершать вызовы асинхронных служб, включая доступ к серверу. Давайте рассмотрим реализацию эффекта, который заканчивает экзамен, рассчитав свой счет. Эффект на начало экзамена более сложный, и его логика будет впитывать на то, чтобы объяснить роль эффектов в логике приложения.
Вспоминая заметки планирования, у нас есть:
Давайте посмотрим на реализацию в файле « Exam-End.effects.ts »:
export class ExamEndEffects { @Effect() public effect$: Observable; constructor( private actions$: Actions, @Inject(MODULE_STORE_TOKEN) private store$: Store, private examEvalService: ExamEvalService, ) { this.effect$ = this.actions$.ofType(ExamEndAction.type) .mergeMap( action => Observable.concat( Observable.of(new ExamStatusAction({ status: action.payload.status })), this.store$ .take(1) .map(getExamData) .filter(a => a !== null) .mergeMap( ({ examInfo, questions }) => { return Observable.concat( Observable .of(new ExamScoreAction({ score: AsyncDataSer.loading() })), this.examEvalService.evalQuestions(examInfo, questions) .map(adata => new ExamScoreAction({ score: adata })), ); }), )); } }
oftype ()
Оператор фильтрует наблюдателя для типа, указанных в результате нового наблюдаемого, который находится на Эффекты $
Собственность класса. Библиотека @ Ngrx/Effects возьмет этот наблюдаемый и отправляет свои действия до завершения с помощью @Effect ()
декоратор.
В ответ на Экзамена
код излучает Экзаменация
немедленно, а затем принимает государство для экзамена и вопросов информации и фильтров для их доступности. Функция getexamdata ()
Экстракты из состояния магазина Информация, которую нам нужна, если это доступно, возвращая нулю в противном случае.
function getExamData(storeState: State) { if (!storeState.exam || !AsyncDataSer.hasData(storeState.exam.data, false) || !storeState.questions || !AsyncDataSer.hasData(storeState.questions.data, false)) return null; return { examInfo: storeState.exam.data.data, questions: storeState.questions.data.data, }; }
Затем код принимает эту информацию и трубы в конкатенации действия Экзаменационный знак
С результатом звонка examevalservice.evalquestions ()
сопоставлен на Экзаменационный знак
действие. оценить ()
Функция асинхронно вычисляет счет.
Эффект для Exam_Start является сущностью, очень похожим на эффект для проверки, только что представленных. Это немного сложнее, но это действительно все больше RXJS Выберите вопросы info с сервера и производства таймера, который может быть прервана, если изменение состояния экзамена в состоянии redux. Я не буду проходить через этот код, поскольку моя цель здесь не стоит показывать, как использовать ReaciveX, чтобы решить проблемы, но чтобы показать, как применить эффекты Redux для реализации некоторой логики вокруг состояния и услуг.
Файлы «Маршрутизатор – *. Effects.s» реализует эффекты для действий навигации, указанные в нижней части части 1 этой серии статей. Единственное главное отличие от начала и конечных эффектов экзамена, заключается в том, что помимо запуска на действия, они проверяют полезную нагрузку на действие, соответствующую эффекту. Им нужно знать, если навигация на определенной странице. Это достигается с помощью библиотеки с именем « Маршрутизатор-магазин-серс ». Смотрите больше на этом репозитории.
Резюме
В этой статье мы только что увидели, как реализовать бизнес-логику в Redux с архитектурой эффектов. Начиная с такого планирования, которую я сделал (обсуждается на предыдущей статье), он легко карта. Я думаю, что единственная сложная часть – это использование реактивныхx к видам декларационально реализует то, что обычно считается императивным способом. Будущая проблема может найти способ сделать планирование уже в декларативной/реактивной форме, что делает реализацию действительно простым.
В следующую статью мы увидим, как все деловая логика может быть тестирована единицами, даже не имея ни одного пользовательского интерфейса.
Повеселись!
PS: Эта статья также появляется на Средний Отказ