浅谈 Angular 和 UI5 这两种前端框架里控件 ID 的设计思路差异

简介: 浅谈 Angular 和 UI5 这两种前端框架里控件 ID 的设计思路差异

最近笔者在工作中,修复了一些我团队负责的 Angular 应用里关于控件 id 的 issue,正好我在从事 Angular 开发之前,使用 UI5 这个前端开发框架也工作了很多年。虽然二者都是优秀的企业级前端应用的开发框架,但二者无论是从设计理念还是开发思路上来说都有着很大的差异。

所谓『管中窥豹,可见一斑』。本文从 UI 控件元素 ID 的生成逻辑这个切入点出发,向大家分享我对这两个前端框架设计理念差异的一些理解。

我们先用 UI5 创建一个简单的 button 控件:

UI5 控件拥有对应的渲染器,比如 Button 的渲染器叫做 ButtonRenderer,负责渲染出如下图高亮的 HTML 代码,其中控件 ID 为 __button0.

对于 UI5 的开发场景来说,一个常用的需求,就是如何使用原生的 JavaScript 代码,触发 UI5 按钮控件的点击事件处理函数?

对于这个需求来说,三个 UI5 Button 控件的 ID,在渲染出的 HTML 代码里分别为 __button0, __button1 和 __button2.

很容易看出 UI5 控件 ID 的格式为:控件对应的名称前缀,再加上一个计数器。

其中控件前缀名称,例如 Button 控件 ID 的前缀为 button, 该前缀是 UI5 控件元数据的一部分:

而 UI5 控件的全局计数器,维护在字典数据结构 mUIDCounts 里,其中 key 为不同的 UI5 控件元数据里存储的前缀,value 为该类型的 UI5 控件当前的计数器值。

迄今为止,我们讨论的都是开发人员在创建 UI5 控件实例时,没有显式指定 ID 的情形。

如果开发人员通过构造函数 ID 参数,显式传入一个 ID:

则最后渲染出的 HTML 源代码里,ID 值不再包含前缀:

这个逻辑在 UI5 控件对应的原型链节点 ManagedObject 的构造函数里可以清楚地看到:

  1. 如果开发人员显式指定了控件 ID,则使用该 ID 渲染 HTML
  2. 如果开发人员没有指定控件 ID,则使用控件元数据里包含的前缀,加上全局计数器自动生成 ID

UI5 控件提供了一个工具方法,.ui.getCore().byId,能够根据控件 ID,返回对应的控件实例。

下面的代码,assert 断言语句能够成功执行:

有的朋友可能会认为, byId 方法最终会交由原生的 DOM API document.getElementById 来执行,事实并非如此。

每个创建好的 UI5 控件实例,都会被添加到 UI5 全局注册表 mElements 中。随后开发人员调用 .ui.getCore().byId 时,该方法直接从注册表 mElements 中查询并返回对应的控件实例即可,其效率高于原生 DOM API document.getElementById.

UI5 的控件实例注册过程,实现在 Core.prototype.registerElement 方法里。下图高亮的第 40705 行代码抛出的错误消息,也解释了为什么 UI5 不允许两个控件拥有相同的 ID. 方法 this.oConfiguration.getNoDuplicateIds 检测到重复 ID 后,会执行相应的处理逻辑。

Angular 虽然和 UI5 一样,也是单页面应用,并且二者都允许并重度依赖自定义控件 (Angular 里称 Component),但二者在视图设计上一个较大的差异就是,Angular Component 的视图实现于原生的 HTML 文件 (或者于内联的 HTML 字符串) 里,而非像 UI5 那样,使用 XML 或者 JavaScript 视图来实现自己的页面布局。

因此,一个前端开发人员,仅凭静态浏览 Angular Component 的 HTML 视图源代码,大致就能判断出最后渲染而成的 HTML 页面源代码:二者相差不大,Angular 没有 UI5 控件渲染器的概念。

例如,下图是 Commerce Cloud 组织架构明细页面 (Organization Unit Detail) 的 HTML 视图源代码:

这个例子使用的完整源代码如下:

<cx-org-card
  *ngIf="model$ | async as model"
  i18nRoot="orgUnit.details"
  [cxFocus]="{ refreshFocus: model }"
  [showHint]="true"
>
  <a
    actions
    class="link edit"
    [class.disabled]="!model.active || (isInEditMode$ | async)"
    [routerLink]="{ cxRoute: 'orgUnitEdit', params: model } | cxUrl"
  >
    {{ 'organization.edit' | cxTranslate }}
  </a>
  <cx-org-toggle-status
    actions
    key="uid"
    i18nRoot="orgUnit"
  ></cx-org-toggle-status>
  <cx-org-disable-info
    info
    i18nRoot="orgUnit"
    [displayInfoConfig]="{ disabledDisable: true }"
  >
  </cx-org-disable-info>
  <section main class="details" cxOrgItemExists>
    <div class="property">
      <label>{{ 'orgUnit.name' | cxTranslate }}</label>
      <span class="value">
        {{ model.name }}
      </span>
    </div>
    <div class="property">
      <label>{{ 'orgUnit.uid' | cxTranslate }}</label>
      <span class="value">
        {{ model.uid }}
      </span>
    </div>
    <div class="property">
      <label>{{ 'orgUnit.active' | cxTranslate }}</label>
      <span class="value" [class.is-active]="model.active">
        {{
          (model.active ? 'organization.enabled' : 'organization.disabled')
            | cxTranslate
        }}
      </span>
    </div>
    <div class="property" *ngIf="model.approvalProcess?.name">
      <label>{{ 'orgUnit.approvalProcess' | cxTranslate }}</label>
      <span class="value">
        {{ model.approvalProcess?.name }}
      </span>
    </div>
    <div class="property" *ngIf="model.parentOrgUnit">
      <label>{{ 'orgUnit.parentUnit' | cxTranslate }}</label>
      <a
        class="value"
        [routerLink]="
          {
            cxRoute: 'orgUnitDetails',
            params: model.parentOrgUnit
          } | cxUrl
        "
      >
        {{ model.parentOrgUnit?.name }}
      </a>
    </div>
  </section>
  <section main class="link-list">
    <ng-container *ngIf="model.uid">
      <a
        class="link"
        [routerLink]="{ cxRoute: 'orgUnitChildren', params: model } | cxUrl"
        routerLinkActive="is-current"
      >
        {{ 'orgUnit.links.units' | cxTranslate }}
      </a>
      <a
        class="link"
        [routerLink]="{ cxRoute: 'orgUnitUserList', params: model } | cxUrl"
        routerLinkActive="is-current"
      >
        {{ 'orgUnit.links.users' | cxTranslate }}
      </a>
      <a
        class="link"
        [routerLink]="{ cxRoute: 'orgUnitApprovers', params: model } | cxUrl"
        routerLinkActive="is-current"
      >
        {{ 'orgUnit.links.approvers' | cxTranslate }}
      </a>
      <a
        class="link"
        [routerLink]="{ cxRoute: 'orgUnitAddressList', params: model } | cxUrl"
        routerLinkActive="is-current"
      >
        {{ 'orgUnit.links.shippingAddresses' | cxTranslate }}
      </a>
      <a
        class="link"
        [routerLink]="{ cxRoute: 'orgUnitCostCenters', params: model } | cxUrl"
        routerLinkActive="is-current"
      >
        {{ 'orgUnit.links.costCenters' | cxTranslate }}
      </a>
    </ng-container>
  </section>
</cx-org-card>

下图是最终渲染出的在浏览器里观测到的 HTML 源代码,同上图相比差异不大。

而 UI5 XML 视图,特别是引入 Fiori Elements 之后,XML 视图的代码同最后渲染出的 HTML 源代码相比,差异巨大。因为渲染过程中,Fiori Elements 根据 OData 上的 Annotation,进行了非常多复杂的处理,后续 Jerry 的公众号会详细介绍。

比如一个 Fiori Elements 应用,只用了 7 行代码,定义了一个 Smart List:

Angular UI 不像 UI5 那样,倾向于为每一个 HTML 元素分配一个不重复的 ID. 下图是 UI5 的 HTML 源代码,能观察到不少 HTML 元素都有一个 Unique ID,而这种情形不会在 Angular 应用的 HTML 源代码里发生。

然而 Angular 有一个结构化指令 ng-template, 也具有通过 # 符号标注的 ID 属性,配合另一个指令 NgIf,能实现页面布局的条件渲染。下图是一个具体例子:

UI5 也有类似 Angular 这种 Template 设计,在 UI5 里称为 ViewFragment. 在 Fiori Elements 的框架实现里,更是重度依赖了 ViewFragment,它能作为容器,将若干逻辑上相关且具有重用可能性的 UI5 控件包裹在一起,方便多个 XML 视图重用。

以上就是笔者工作多年使用 Angular 和 UI5 后的一些感悟,希望对使用这两个前端框架工作的同行们有所帮助。

相关文章
|
14天前
|
前端开发 JavaScript 开发者
Express.js与前端框架的集成:React、Vue和Angular的示例与技巧
本文介绍了如何将简洁灵活的Node.js后端框架Express.js与三大流行前端框架——React、Vue及Angular进行集成,以提升开发效率与代码可维护性。文中提供了详细的示例代码和实用技巧,展示了如何利用Express.js处理路由和静态文件服务,同时在React、Vue和Angular中构建用户界面,帮助开发者快速掌握前后端分离的开发方法,实现高效、灵活的Web应用构建。
31 3
|
1月前
|
前端开发 JavaScript 开发者
震惊!Web 前端 href 与 src 竟有如此差异,快来一探究竟,掌握热门技术核心要点
【8月更文挑战第26天】在Web前端开发中,`href`与`src`是两个常用属性,但其差异常被忽视。`href`(超文本引用)用于创建文档间的链接关系,如链接至外部网页或引入CSS文件;`src`(来源)则用于在文档内嵌入资源,如图片或JavaScript文件。两者在使用场景及加载机制上有所不同:`href`支持并行下载且不阻塞渲染,适合非关键资源加载;而`src`加载时会暂停页面渲染直至资源加载完成,适用于如图片和脚本这类对页面显示至关重要的资源。因此,正确理解并运用这两个属性对于保障网页性能和用户体验至关重要。
41 3
|
1月前
|
存储 前端开发 JavaScript
Web前端的奇幻之旅:探索JS数据类型的奥秘与差异
【8月更文挑战第23天】JavaScript是一种动态类型语言,提供多种内置数据类型支持信息的存储与操作。这些类型对Web前端开发者至关重要,直接影响代码性能与可读性。JavaScript数据类型主要分为两大类:原始数据类型(如Undefined、Null、Boolean等)与引用数据类型(如Object、Array等)。原始类型直接存储值,而引用类型存储指向数据的引用。原始类型不可变且存储在栈中,访问更快;引用类型则存储在堆中,可通过其引用进行修改。理解这些差异有助于编写高效、可维护的代码。
30 0
|
1月前
|
C# 开发者 Windows
一款基于Fluent设计风格、现代化的WPF UI控件库
一款基于Fluent设计风格、现代化的WPF UI控件库
|
27天前
|
前端开发 开发者 开发框架
JSF与Bootstrap,打造梦幻响应式网页!让你的应用跨设备,让用户爱不释手!
【8月更文挑战第31天】在现代Web应用开发中,响应式设计至关重要,以确保不同设备上的良好用户体验。本文探讨了JSF(JavaServer Faces)与Bootstrap框架的结合使用,展示了如何构建响应式网页。JSF是一个基于Java的Web应用框架,提供丰富的UI组件和表单处理功能;而Bootstrap则是一个基于HTML、CSS和JavaScript的前端框架,专注于实现响应式设计。通过结合两者的优势,开发者能够更便捷地创建自适应布局,提升Web应用体验。然而,这种组合也有其局限性,如JSF组件库较小和较高的学习成本等,因此在选择开发框架时需综合考虑具体需求和应用场景。
31 0
|
27天前
|
前端开发 开发者 C#
深度解析 Uno Platform 中的 MVVM 模式:从理论到实践的全方位指南,助你轻松掌握通过 C# 与 XAML 构建高效可维护的跨平台应用秘籍
【8月更文挑战第31天】本文详细介绍如何在优秀的跨平台 UI 框架 Uno Platform 中实施 MVVM(Model-View-ViewModel)模式,通过一个简单的待办事项列表应用演示其实现过程。MVVM 模式有助于分离视图层与业务逻辑层,提升代码组织性、易测性和可维护性。Uno Platform 的数据绑定机制使视图与模型间的同步变得高效简便。文章通过构造 `TodoListViewModel` 类及其相关视图,展示了如何解耦视图与模型,实现动态数据绑定及命令处理,从而提高代码质量和开发效率。通过这一模式,开发者能更轻松地构建复杂的跨平台应用。
26 0
|
2月前
|
前端开发 容器
前端框架与库 - Angular模块与依赖注入
【7月更文挑战第17天】探索Angular的模块化和依赖注入:模块用于组织组件、服务等,通过`@NgModule`声明。依赖注入简化类间依赖管理,但面临模块重复导入、服务作用域不当和依赖循环等问题。解决策略包括规划模块结构、正确设置服务作用域和使用工厂函数打破循环依赖。遵循最佳实践,构建高效、可维护的Angular应用。
56 17
|
2月前
|
编解码 前端开发 UED
UI/UX设计在前端开发中的重要性
【7月更文挑战第27天】综上所述,UI/UX设计在前端开发中具有不可替代的重要性。它们不仅决定了产品的视觉呈现和交互体验,还影响了用户的满意度、品牌形象、转化率和技术创新等多个方面。因此,在前端开发过程中,我们应该高度重视UI/UX设计的作用和价值,与设计师紧密合作共同打造出优秀的产品。
|
1月前
|
缓存 前端开发 JavaScript
前端框架选择指南:React vs Vue vs Angular
React、Vue与Angular是主流前端框架,各有千秋。React专注视图层,学习曲线平缓,生态丰富,适用于中大型项目;Vue简洁易学,模板直观,内置状态管理,适合中小项目及快速原型;Angular全栈框架,结构严谨,适合大型企业应用。React需手动处理状态管理,Vue提供完整CLI加速开发,Angular虽学习曲线陡峭但提供全套解决方案。性能方面,三者均利用虚拟DOM优化渲染。社区支持上,React最为庞大,Vue活跃度高,Angular有Google背书。选型应基于项目需求、团队技能及维护考量。
44 0
|
2月前
|
前端开发 JavaScript
前端框架与库 - Angular基础:组件、模板、服务
【7月更文挑战第16天】Angular,谷歌维护的前端框架,专注构建动态Web应用。组件是核心,包含行为逻辑的类、定义视图的模板和样式。模板语法含插值、属性和事件绑定。服务提供业务逻辑,依赖注入实现共享。常见问题涉及组件通信、性能和服务注入。优化通信、性能并正确管理服务范围,能提升应用效率和质量。学习组件、模板和服务基础,打造高效Angular应用。
58 1

热门文章

最新文章