浅谈 Angular 应用前端消息显示机制功能实现背后的技术原理

笔者在一个开源的名叫 Spartacus 的电商框架项目上,已经工作三年多了。

这是这个开源项目在 Github 上的仓库,本项目基于 Angular 框架开发而成:github.com/SAP/spartac...

本文分享笔者近日完成的一个关于消息显示的需求实现的一些经验。

这个需求来自 StackOverflow 社区上一位 Spartacus 的使用者的一个定制化实现时遇到的问题:

这个需求的背景是,客户在 SAP 电商云的产品明细页面,可以留下自己的评论,点击 Submit 按钮提交。

提交之后,能看到"谢谢评论"的提示消息。

客户定制化需求是:不执行这个默认的消息显示逻辑,即不显示消息,而是执行其他逻辑,比如短信通知或邮件通知。

目前 Spartacus 默认显示 谢谢评论的代码如下:

typescript 复制代码
  showGlobalMessageOnPostProductReviewSuccess$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(ProductActions.POST_PRODUCT_REVIEW_SUCCESS),
        tap(() => {
          this.globalMessageService.add(
            { key: 'productReview.thankYouForReview' },
            GlobalMessageType.MSG_TYPE_CONFIRMATION
          );
        })
      ),
    { dispatch: false }
  );

"感谢评论"的消息文本 ID 为 productReview.thankYouForReview,在 ProductReviewEffects 接收到 POST_PRODUCT_REVIEW_SUCCESS 这个 Action 之后通过 MessageService 显示到 UI 上。

一种最常规的二次开发思路就是,继承 SAP Commerce Cloud UI 标准发布的 ProductReviewEffects 类,重载其接收到 POST_PRODUCT_REVIEW_SUCCESS 之后的实现代码,将调用 MessageService 抛出感谢评论的代码删除掉即可。

然而 SAP Commerce Cloud UI 发布的所有 Effects 实现类都是不可扩展的,因此这条思路行不通。

SAP 电商云 Spartacus UI,同 Commerce 后台交互的逻辑:

Spartacus 同 Commerce 系统的通信,通过 HTTP 协议调用 OCC API 完成。Connector 是 HTTP调用的发起者,维护了静态的配置信息,即 API endpoint.

比如,从 Commerce 系统读取产品主数据,读取的字段列表以 url 参数的形式出现在 API endpoint 里。这些字段列表可以在 Connector 的静态配置点里进行配置。

Connector 并不会直接同 Commerce 交互,而是把请求转发给 Adapter,具体通信由 Adapter 完成,Connector 只负责调度 Adapter. Spartacus 发布的 Adapter 默认使用 OCC Module,即 Commerce 标准的 OCC Restful API,但是客户也可以实现自己的 Adapter,连接 Commerce 之外的其他后台系统。

Connector 将 Adapter 取回的数据交给 NgRx Store 结构统一管理,后者的复杂度被 Facade 层所隐藏,而Spartacus UI 组件只会同 Facade 层交互,进行数据绑定和页面展示。这体现了关注点分离的设计原则。

为了实现该需求,我们需要深入了解下图红色高亮区域即 NgRx Store 和 SAP Spartacus Connector 交互的细节。

NgRx 是 Angular 基于 RxJs 的一个响应式状态管理库,包含下列核心概念,笔者会结合 SAP Commerce Cloud UI 对 NgRx 的实际使用情况来举例说明。

  • Action:其实就是编程领域的事件的别名。SAP Commerce Cloud UI 组件,能响应用户操作,通过组件的 Service 实例,投递出相应的 Action. Action 投递方和 Action 接收方是解耦的,彼此感知不到对方。 下图是 SAP 电商云产品明细页面评论区域的 Submit 按钮被点击之后,对应的 Service 类抛出 PostProductReview Action 的代码:
  • ProductActions.PostProductReview 是 NgRx Action 的一个子类,type 字段为 POST_PRODUCT_REVIEW,构造函数的参数 payload,定义了调用 Commerce OCC API 持久化用户评论需要传递的数据结构:
  • Effects:SAP Commerce Cloud UI Action 的接收方之一。下图 Effects 代码的语义是:接收类型为 POST_PRODUCT_REVIEW 的 Action(第46行),调用前文介绍的 Connector,向 Commerce 后台发起 OCC API 调用(第49行),根据 API 返回结果,分别投递评论保存成功或失败的 Action:

  • PostProductReviewSuccess(第53行)

  • PostProductReviewFail(第56行)

  • Store:Angular 应用维护在内存中的存储结构,存放了 SAP Commerce Cloud UI 所有组件的运行时数据。每当 Effects 调用 Commerce OCC API 拿到新的数据时,调用 Reducer,将增量数据填充到 Store 中去。

  • Reducer:本质上是一个有限状态自动机,每当收到代表来自 Commerce 后台的数据发生变化的 Action 时,状态机驱动对应的 Reducer, 根据新的 Action 包含的负载,对 Store 中的数据进行调整。 例如产品明细页面第一次渲染时,Effects 需要从 Commerce 后台读取所有的评论数据。读取成功后,抛出 LOAD_PRODUCT_REVIEWS_SUCCESS Action. 下图的 ProductReviewsReducer 接收到该 Action,从其 payload 中解析出实际评论数据并返回。这些返回的数据会被 NgRx 框架接收,并合并到 Store 中去。

  • Selector:纯函数,作为应用程序从 NgRx Store 中读取最新数据的接口。

理清楚 SAP Commerce Cloud UI 使用 NgRx 进行状态管理的细节之后,对于文章开头的客户定制化需求,也就不难实现了。

既然下图所示的 SAP Commerce Cloud UI 标准的 Effects 无法扩展,我们注意到 UI 组件的 Service 层,才是所有事件流的起始点,因此可以实现新的 Service,来替换 SAP 电商云 UI 标准的 Service,然后基于该定制化 Service,实现配套的 Effects 和 Action.

如此一来,标准的和用户评论相关的逻辑流(如下图 1-2-3 所示)将不会再得到执行,取而代之的是我们自己定制化的执行流,如下图 A-B-C 所示,其中蓝色的图例均为 Partners 需要开发的定制化代码。

这个解决方案的简要介绍: (1) 创建新的 Action CustPostProductReview,CustPostProductReviewSuccess 和 CustPostProductReviewFail. 其实就是在标准的实现类之前,添加 Cust 的前缀。 (2) 创建新的 CustProductReviewService,继承标准的 ProductReviewService. 重载其 add 方法,抛出新的 CustPostProductReview Action.

(3) 新建 CustProductReviewsEffects,接收新的 Action CUST_POST_PRODUCT_REVIEW(第25行),仍旧调用 Connector 将用户评论持久化到 Commerce 后台(第28行),然后抛出新的 Action CustPostProductReviewSucess.

自定义 Effect 接收到新的 Action CUST_POST_PRODUCT_REVIEW_SUCCESS 之后,就可以进行自定义逻辑编写。

(4)将自定义的 Effect 和 Service 实现,通过 Angular 依赖注入框架,配置到对应的 Module 中。

本需求在 SAP Spartacus 3.1 版本测试通过。

总结

本文通过一个实际需求的实现案例,详细介绍了 Angular Ngrx 里 Effect,Connector,Adapter,Reducer,Selector 和 Store 等概念的交互关系,希望对需要实现类似功能的同行有所帮助。

相关推荐
csdnLN14 分钟前
$.ajax() 对应事件done() 、fail()、always() 的用法
前端·javascript·ajax
甜味橘阳15 分钟前
echarts地图可视化展示
前端·javascript·echarts
bloxed1 小时前
前端文件下载多方式集合
前端·filedownload
余生H1 小时前
前端Python应用指南(三)Django vs Flask:哪种框架适合构建你的下一个Web应用?
前端·python·django
LUwantAC1 小时前
CSS(四)display和float
前端·css
cwtlw1 小时前
CSS学习记录20
前端·css·笔记·学习
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
米奇妙妙wuu2 小时前
react使用sse流实现chat大模型问答,补充css样式
前端·css·react.js
傻小胖2 小时前
React 生命周期完整指南
前端·react.js
梦境之冢2 小时前
axios 常见的content-type、responseType有哪些?
前端·javascript·http