如何将你的 Angular 和 NgRx 应用升级到 v8
概述
升级依赖项
通过实例学习——一家水果店(NgRx v7)
完整视频演示
结论
概述
您是否拥有一个使用 Angular v7 和 NgRx v7 编写的出色应用程序,却苦于错过网上和各种会议上关于 Angular v8 和 NgRx v8 的讨论?别担心,您来对地方了!今天,我们将一起探索如何使用 Angular CLI 工具将应用程序升级到 Angular v8,并了解如何升级到 NgRx v8。这将使我们能够充分利用 NgRx v8 的新特性。NgRx v8 包含一套强大的创建器(或类型安全的工厂函数),用于创建 action、effect 和 reducer。
本文改编自Ultimate Courses网站上的一篇原创文章。
升级依赖项
升级 Angular
Angular 团队提供了一个非常棒的网站,详细介绍了升级过程。该网站地址为Angular Update Tool。今天我们将介绍其中的一些信息。
第一步是将我们的应用程序升级到Angular v8。我们将使用Angular CLI来管理这个过程。
这是首选方法,因为 Angular 提供了内置的迁移脚本或示意图,可以减轻我们简单地更新版本时所涉及的一些手动过程package.json。
首先,请在终端中运行以下命令:
更新全局 Angular CLI 版本
npm install -g @angular/cli
将核心框架和本地 CLI 更新到 v8 版本
ng update @angular/cli @angular/core
在此过程中,我们可能会遇到第三方库的问题。遇到这种情况,最好访问这些库的 GitHub issues 和代码仓库以寻求解决方案。
升级 NgRx
现在我们已经将应用程序升级到使用 Angular v8,接下来让我们将 NgRx 也更新到 v8。这里我们也会用到 Angular CLI。
将 NgRx 更新到 v8 版本
ng update @ngrx/store
先前的命令应该更新我们的package.json依赖项并运行任何 NgRx 提供的迁移,以保持我们的应用程序正常运行。
根据您的配置,NgRxng update @ngrx/store可能不会自动更新您安装的其他@ngrx/*库。如果发生这种情况,最好的办法是手动npm install为每个与 NgRx 一起使用的其他模块运行更新。
例如:
npm install @ngrx/entity@latest
npm install @ngrx/effects@latest
npm install @ngrx/data@latest
npm install @ngrx/router-store@latest
NgRx迁移指南
NgRx 团队提供了一份详细的迁移指南,指导用户如何升级到 NgRx v8。更多关于升级到 NgRx v8 的信息,请参阅:V8 更新指南
通过实例学习——一家水果店(NgRx v7)
学习新方法最流行的途径之一就是通过代码示例。让我们来探讨以下简化的 NgRx store 示例,它存储着一系列array对象Fruit。
每个Fruit对象由三个属性组成fruitId,fruitClass和fruitName。
interface Fruit {
fruitId: number;
fruitType: string;
fruitName: string;
}
例如,如果我们有一个orange,它可能看起来像这样:
const orange: Fruit = {
fruitId: 1,
fruitType: 'citrus',
fruitName: 'orange'
};
状态
进一步探索,我们State在 NgRx 存储中的对象将包含诸如fruits、isLoading和 之类的属性errorMessage。
fruits被定义为array对象Fruit的isLoading是boolean用来跟踪商店何时正在从外部 API 加载数据的。errorMessage除非在从外部 API 请求数据时发生错误,否则该string属性将保持不变。null
例如State interface:
interface State {
fruits: Fruit[];
isLoading: boolean;
errorMessage: string;
}
一个加载完毕的示例商店fruits可能如下所示:
const state: State = {
fruits: [
{
fruitId: 1,
fruitType: 'citrus',
fruitName: 'orange'
}
],
isLoading: false,
errorMessage: null
}
行动
遵循 Redux 的规范模式,我们不能直接更新 state,因此需要定义一组 actions,并通过 reducer 来操作 state。假设在这个例子中我们有 3 个 actions:
-
[App Init] Load Request此操作旨在从我们的 UI 层分发,以表明我们正在请求将Fruit对象加载到我们的存储中。此操作没有有效负载或props。 -
[Fruits API] Load Success[App Init] Load Request此操作旨在当一个事件被分发、API 被调用且 API 收到成功响应时,从我们的效果中分发。此操作包含一个有效负载或props对象,其中包含要加载到我们存储中的对象array的属性。Fruit -
[Fruits API] Load Failure此操作旨在当一个事件[App Init] Load Request被触发、API 被调用且收到 API 的失败响应时,从我们的效果中分发出去。此操作包含一个有效负载或props对象,其中包含我们 API 请求的错误消息,以便将其加载到我们的存储中。
NgRx v7 实现
我们操作在 NgRx v7 中的实际实现可能如下所示:
import { Action } from '@ngrx/store';
import { Fruit } from '../../models';
export enum ActionTypes {
LOAD_REQUEST = '[App Init] Load Request',
LOAD_FAILURE = '[Fruits API] Load Failure',
LOAD_SUCCESS = '[Fruits API] Load Success'
}
export class LoadRequestAction implements Action {
readonly type = ActionTypes.LOAD_REQUEST;
}
export class LoadFailureAction implements Action {
readonly type = ActionTypes.LOAD_FAILURE;
constructor(public payload: { error: string }) {}
}
export class LoadSuccessAction implements Action {
readonly type = ActionTypes.LOAD_SUCCESS;
constructor(public payload: { fruits: Fruit[] }) {}
}
export type ActionsUnion = LoadRequestAction | LoadFailureAction | LoadSuccessAction;
NgRx v8 - 升级到 createAction
值得注意的是,虽然
createAction是 NgRx 中定义联合类型的热门新方法,但定义联合类型并导出联合类型的Action现有方法在 NgRx v8 中仍然有效。enumclass
从 NgRx 版本 8 开始,可以使用新方法声明操作createAction。此方法是一个 `Action` factory function,或者是一个function返回 `Action` 的对象function。
根据NgRx 官方文档,“该createAction函数返回一个函数,该函数被调用时会返回一个符合接口形状的对象Action。该props方法用于定义处理操作所需的任何其他元数据。操作创建器提供了一种一致且类型安全的方式来构造要分发的操作。”
要更新到最新版本createAction,我们需要执行以下步骤:
- 为我们的操作创建一个新的操作
export const。如果我们的操作包含有效负载,我们也需要迁移到使用该props方法来定义我们的有效负载props。
例如[App Init] Load Request
// before
export class LoadRequestAction implements Action {
readonly type = ActionTypes.LOAD_REQUEST;
}
// after
export const loadRequest = createAction('[App Init] Load Request');
例如[Fruits API] Load Success
// before
export class LoadSuccessAction implements Action {
readonly type = ActionTypes.LOAD_SUCCESS;
constructor(public payload: { fruits: Fruit[] }) {}
}
// after
export const loadSuccess = createAction('[Fruits API] Load Success', props<{fruits: Fruit[]}>());
-
从中移除旧操作。
ActionTypesenum -
从中移除旧操作。
ActionsUnion
我们最终迁移后的操作文件可能如下所示:
import { Action, props } from '@ngrx/store';
import { Fruit } from '../../models';
export const loadRequest = createAction('[App Init] Load Request');
export const loadFailure = createAction('[Fruits API] Load Failure', props<{errorMessage: string}>());
export const loadSuccess = createAction('[Fruits API] Load Success', props<{fruits: Fruit[]}>());
我们可以看到,代码量大幅减少,从 24 行代码减少到 6 行代码。
NgRx v8 - 分发 createAction 操作
最后需要说明的是,我们需要更新分发 action 的方式。这是因为我们不再需要创建class实例,而是调用factory返回 action 对象的函数。
我们的改造前后对比图大概会是这样的:
// before
this.store.dispatch(new featureActions.LoadSuccessAction({ fruits }))
// after
this.store.dispatch(featureActions.loadSuccess({ fruits }))
减速器
继续之前的例子,我们需要设置一个 reducer 来处理 store 的更新。回顾 Redux 模式,我们不能直接更新 state。我们必须通过一个纯函数,接收当前 state 和一个 action,并返回应用了该 action 的更新后的新 state。通常,reducer 是一个switch以传入 action 为键的大型语句。
假设我们的 reducer 处理以下场景:
-
我们希望该
[App Init] Load Request状态体现以下价值观:state.isLoading: truestate.errorMessage: null
-
我们希望该
[Fruits API] Load Success状态体现以下价值观:state.isLoading: falsestate.errorMessage: nullstate.fruits: action.payload.fruits
-
我们希望该
[Fruits API] Load Failure状态体现以下价值观:state.isLoading: falsestate.errorMessage: action.payload.errorMessage
NgRx v7 实现
我们的 reducer 在 NgRx v7 上的实际实现可能如下所示:
import { ActionsUnion, ActionTypes } from './actions';
import { initialState, State } from './state';
export function featureReducer(state = initialState, action: ActionsUnion): State {
switch (action.type) {
case ActionTypes.LOAD_REQUEST: {
return {
...state,
isLoading: true,
errorMessage: null
};
}
case ActionTypes.LOAD_SUCCESS: {
return {
...state,
isLoading: false,
errorMessage: null,
fruits: action.payload.fruits
};
}
case ActionTypes.LOAD_FAILURE: {
return {
...state,
isLoading: false,
errorMessage: action.payload.errorMessage
};
}
default: {
return state;
}
}
}
NgRx v8 - 升级到 createReducer
值得注意的是,虽然
createReducer是 NgRx 中定义 reducer 的热门新方法,但在 NgRx v8 中,使用语句定义 reducer 的现有方法function仍然switch有效。
从 NgRx 版本 8 开始,可以使用新方法声明 reducer createReducer。
根据NgRx 官方文档,“reducer 函数的职责是以不可变的方式处理状态转换。创建一个 reducer 函数来处理使用该createReducer函数管理状态的操作。”
要更新到最新版本createReducer,我们需要执行以下步骤:
const reducer = createReducer为我们的 reducer创建一个新的。- 将我们的语句转换
switchcase为on方法调用。请注意,default系统会自动处理大小写。方法的第一个参数on是要触发的操作,第二个参数是一个处理程序,它接收state并返回新版本state。如果操作提供了 `propsreturn` 属性,则可以提供第二个可选的输入参数。在下面的示例中,我们将使用解构赋值从对象中提取必要的属性props。 - 创建一个新的
export function reducer包装来const reducer提供AOT支撑。
更新完成后,界面featureReducer将类似于以下示例:
import { createReducer, on } from '@ngrx/store';
import * as featureActions from './actions';
import { initialState, State } from './state';
...
const featureReducer = createReducer(
initialState,
on(featureActions.loadRequest, state => ({ ...state, isLoading: true, errorMessage: null })),
on(featureActions.loadSuccess, (state, { fruits }) => ({ ...state, isLoading: false, errorMessage: null, fruits })),
on(featureActions.loadFailure, (state, { errorMessage }) => ({ ...state, isLoading: false, errorMessage: errorMessage })),
);
export function reducer(state: State | undefined, action: Action) {
return featureReducer(state, action);
}
效果
因为我们希望 reducer 保持纯函数状态,所以通常需要将 API 请求放入 reducer 中side-effects。在 NgRx 中,这些 reducer 被称为Effectsreducer,它提供了一种基于 RxJS 的响应式方法,可以将 action 链接到可观察流。
在我们的示例中,我们将有一个用于操作的Effect按钮,并向我们假想的后端发出 HTTP 请求。listens[App Init] Load RequestFruits API
-
如果响应成功,
Fruits API则将其映射到[Fruits API] Load Success操作,并将有效载荷设置fruits为成功响应的主体。 -
如果执行失败,则
Fruits API错误消息会映射到一个[Fruits API] Load Failure操作,该操作的有效负载设置errorMessage为失败响应中的错误。
NgRx v7 实现
我们的效果在 NgRx v7 中的实际实现可能如下所示:
@Effect()
loadRequestEffect$: Observable<Action> = this.actions$.pipe(
ofType<featureActions.LoadRequestAction>(
featureActions.ActionTypes.LOAD_REQUEST
),
concatMap(action =>
this.dataService
.getFruits()
.pipe(
map(
fruits =>
new featureActions.LoadSuccessAction({
fruits
})
),
catchError(error =>
observableOf(new featureActions.LoadFailureAction({ errorMessage: error.message }))
)
)
)
);
NgRx v8 - 升级到 createEffect
值得注意的是,虽然这
createEffect是在 NgRx 中定义 reducer 的一种热门新方法,但@Effect()在 NgRx v8 中,使用装饰器定义类属性的现有方法仍然有效。
createEffect根据NgRx 官方文档,从 NgRx 版本 8 开始,可以使用新方法声明效果。
要更新到最新版本createEffect,我们需要执行以下步骤:
- 从
createEffect@ngrx/effects - 移除
@Effect()装饰器 - 移除
Observable<Action>类型注解 this.actions$.pipe(...)用……包裹createEffect(() => ...)- 移除
<featureActions.LoadRequestAction>类型注解ofType - 将
ofType输入参数从更改featureActions.ActionTypes.LOAD_REQUEST为featureActions.loadRequest - 更新操作调用,移除
new并使用创建者而不是class实例。例如,new featureActions.LoadSuccessAction({fruits})变为featureActions.loadSuccess({fruits})。
更新完成后,界面loadRequestEffect将类似于以下示例:
loadRequestEffect$ = createEffect(() => this.actions$.pipe(
ofType(featureActions.loadRequest),
concatMap(action =>
this.dataService
.getFruits()
.pipe(
map(fruits => featureActions.loadSuccess({fruits})),
catchError(error =>
observableOf(featureActions.loadFailure({ errorMessage: error.message }))
)
)
)
)
);
完整视频演示
如果您想观看完整的视频演示,请点击这里。
结论
本指南到此结束。希望您已经了解如何将应用程序升级到 Angular v8 和 NgRx v8。此外,您应该能够自信地利用 NgRx v8 中的一些新特性来减少一些所谓的样板代码。祝您升级顺利!
文章来源:https://dev.to/angular/how-to-upgrade-your-angular-and-ngrx-apps-to-v8-4iip