发布于 2026-01-06 2 阅读
0

如何将 Angular 和 NgRx 应用升级到 v8 概述 升级依赖项 通过示例学习 - 一个水果店(NgRx v7) 完整视频演示 结论

如何将你的 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
Enter fullscreen mode Exit fullscreen mode

将核心框架和本地 CLI 更新到 v8 版本

ng update @angular/cli @angular/core
Enter fullscreen mode Exit fullscreen mode

在此过程中,我们可能会遇到第三方库的问题。遇到这种情况,最好访问这些库的 GitHub issues 和代码仓库以寻求解决方案。

升级 NgRx

现在我们已经将应用程序升级到使用 Angular v8,接下来让我们将 NgRx 也更新到 v8。这里我们也会用到 Angular CLI。

将 NgRx 更新到 v8 版本

ng update @ngrx/store
Enter fullscreen mode Exit fullscreen mode

先前的命令应该更新我们的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
Enter fullscreen mode Exit fullscreen mode

NgRx迁移指南

NgRx 团队提供了一份详细的迁移指南,指导用户如何升级到 NgRx v8。更多关于升级到 NgRx v8 的信息,请参阅:V8 更新指南

通过实例学习——一家水果店(NgRx v7)

学习新方法最流行的途径之一就是通过代码示例。让我们来探讨以下简化的 NgRx store 示例,它存储着一系列array对象Fruit

每个Fruit对象由三个属性组成fruitIdfruitClassfruitName

interface Fruit {
    fruitId: number;
    fruitType: string;
    fruitName: string;
}
Enter fullscreen mode Exit fullscreen mode

例如,如果我们有一个orange,它可能看起来像这样:

const orange: Fruit = {
    fruitId: 1,
    fruitType: 'citrus',
    fruitName: 'orange'
};
Enter fullscreen mode Exit fullscreen mode

状态

进一步探索,我们State在 NgRx 存储中的对象将包含诸如fruitsisLoading和 之类的属性errorMessage

  • fruits被定义为array对象Fruit
  • isLoadingboolean用来跟踪商店何时正在从外部 API 加载数据的。
  • errorMessage除非在从外部 API 请求数据时发生错误,否则string属性将保持不变。null

例如State interface

interface State {
    fruits: Fruit[];
    isLoading: boolean;
    errorMessage: string;
}
Enter fullscreen mode Exit fullscreen mode

一个加载完毕的示例商店fruits可能如下所示:

const state: State = {
    fruits: [
        {
            fruitId: 1,
            fruitType: 'citrus',
            fruitName: 'orange'
        }
    ],
    isLoading: false,
    errorMessage: null
}
Enter fullscreen mode Exit fullscreen mode

行动

遵循 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;
Enter fullscreen mode Exit fullscreen mode

NgRx v8 - 升级到 createAction

值得注意的是,虽然createAction是 NgRx 中定义联合类型的热门新方法,但定义联合类型并导出联合类型的Action现有方法在 NgRx v8 中仍然有效。enumclass

从 NgRx 版本 8 开始,可以使用新方法声明操作createAction。此方法是一个 `Action` factory function,或者是一个function返回 `Action` 的对象function

根据NgRx 官方文档,“该createAction函数返回一个函数,该函数被调用时会返回一个符合接口形状的对象Action。该props方法用于定义处理操作所需的任何其他元数据。操作创建器提供了一种一致且类型安全的方式来构造要分发的操作。”

要更新到最新版本createAction,我们需要执行以下步骤:

  1. 为我们的操作创建一个新的操作export const。如果我们的操作包含有效负载,我们也需要迁移到使用该props方法来定义我们的有效负载props

例如[App Init] Load Request

// before
export class LoadRequestAction implements Action {
  readonly type = ActionTypes.LOAD_REQUEST;
}
Enter fullscreen mode Exit fullscreen mode
// after
export const loadRequest = createAction('[App Init] Load Request');
Enter fullscreen mode Exit fullscreen mode

例如[Fruits API] Load Success

// before
export class LoadSuccessAction implements Action {
  readonly type = ActionTypes.LOAD_SUCCESS;
  constructor(public payload: { fruits: Fruit[] }) {}
}
Enter fullscreen mode Exit fullscreen mode
// after
export const loadSuccess = createAction('[Fruits API] Load Success', props<{fruits: Fruit[]}>());
Enter fullscreen mode Exit fullscreen mode
  1. 从中移除旧操作。ActionTypes enum

  2. 从中移除旧操作。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[]}>());
Enter fullscreen mode Exit fullscreen mode

我们可以看到,代码量大幅减少,从 24 行代码减少到 6 行代码。

NgRx v8 - 分发 createAction 操作

最后需要说明的是,我们需要更新分发 action 的方式。这是因为我们不再需要创建class实例,而是调用factory返回 action 对象的函数。

我们的改造前后对比图大概会是这样的:

// before 
this.store.dispatch(new featureActions.LoadSuccessAction({ fruits }))

// after
this.store.dispatch(featureActions.loadSuccess({ fruits }))
Enter fullscreen mode Exit fullscreen mode

减速器

继续之前的例子,我们需要设置一个 reducer 来处理 store 的更新。回顾 Redux 模式,我们不能直接更新 state。我们必须通过一个纯函数,接收当前 state 和一个 action,并返回应用了该 action 的更新后的新 state。通常,reducer 是一个switch以传入 action 为键的大型语句。

假设我们的 reducer 处理以下场景:

  • 我们希望该[App Init] Load Request状态体现以下价值观:

    • state.isLoading: true
    • state.errorMessage: null
  • 我们希望该[Fruits API] Load Success状态体现以下价值观:

    • state.isLoading: false
    • state.errorMessage: null
    • state.fruits: action.payload.fruits
  • 我们希望该[Fruits API] Load Failure状态体现以下价值观:

    • state.isLoading: false
    • state.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;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

NgRx v8 - 升级到 createReducer

值得注意的是,虽然createReducer是 NgRx 中定义 reducer 的热门新方法,但在 NgRx v8 中,使用语句定义 reducer 的现有方法function仍然switch有效。

从 NgRx 版本 8 开始,可以使用新方法声明 reducer createReducer

根据NgRx 官方文档,“reducer 函数的职责是以不可变的方式处理状态转换。创建一个 reducer 函数来处理使用该createReducer函数管理状态的操作。”

要更新到最新版本createReducer,我们需要执行以下步骤:

  1. const reducer = createReducer为我们的 reducer创建一个新的。
  2. 将我们的语句转换switch caseon方法调用。请注意,default系统会自动处理大小写。方法的第一个参数on是要触发的操作,第二个参数是一个处理程序,它接收state并返回新版本state。如果操作提供了 ` propsreturn` 属性,则可以提供第二个可选的输入参数。在下面的示例中,我们将使用解构赋值从对象中提取必要的属性props
  3. 创建一个新的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);
}
Enter fullscreen mode Exit fullscreen mode

效果

因为我们希望 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 }))
          )
        )
    )
  );
Enter fullscreen mode Exit fullscreen mode

NgRx v8 - 升级到 createEffect

值得注意的是,虽然这createEffect是在 NgRx 中定义 reducer 的一种热门新方法,但@Effect()在 NgRx v8 中,使用装饰器定义类属性的现有方法仍然有效。

createEffect根据NgRx 官方文档,从 NgRx 版本 8 开始,可以使用新方法声明效果

要更新到最新版本createEffect,我们需要执行以下步骤:

  1. createEffect@ngrx/effects
  2. 移除@Effect()装饰器
  3. 移除Observable<Action>类型注解
  4. this.actions$.pipe(...)用……包裹createEffect(() => ...)
  5. 移除<featureActions.LoadRequestAction>类型注解ofType
  6. ofType输入参数从更改featureActions.ActionTypes.LOAD_REQUESTfeatureActions.loadRequest
  7. 更新操作调用,移除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 }))
                )
            )
        )
    )
  );
Enter fullscreen mode Exit fullscreen mode

完整视频演示

如果您想观看完整的视频演示,请点击这里。

结论

本指南到此结束。希望您已经了解如何将应用程序升级到 Angular v8 和 NgRx v8。此外,您应该能够自信地利用 NgRx v8 中的一些新特性来减少一些所谓的样板代码。祝您升级顺利!

文章来源:https://dev.to/angular/how-to-upgrade-your-angular-and-ngrx-apps-to-v8-4iip