浅谈 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 后的一些感悟,希望对使用这两个前端框架工作的同行们有所帮助。

相关文章
|
8天前
|
前端开发 开发者 UED
前端只是切图仔?来学学给开发人看的UI设计
该文章针对前端开发者介绍了UI设计的基本原则与实践技巧,覆盖了布局、色彩理论、字体选择等方面的知识,并提供了设计工具和资源推荐,帮助开发者提升产品的视觉与交互体验。
|
9天前
|
前端开发 JavaScript API
React、Vue.js 和 Angular前端三大框架对比与选择
前端框架是用于构建用户界面的工具和库,它提供组件化结构、数据绑定、路由管理和状态管理等功能,帮助开发者高效地创建和维护 web 应用的前端部分。常见的前端框架如 React、Vue.js 和 Angular,能够提高开发效率并促进团队协作。
25 4
|
23天前
|
前端开发 JavaScript 开发者
Express.js与前端框架的集成:React、Vue和Angular的示例与技巧
本文介绍了如何将简洁灵活的Node.js后端框架Express.js与三大流行前端框架——React、Vue及Angular进行集成,以提升开发效率与代码可维护性。文中提供了详细的示例代码和实用技巧,展示了如何利用Express.js处理路由和静态文件服务,同时在React、Vue和Angular中构建用户界面,帮助开发者快速掌握前后端分离的开发方法,实现高效、灵活的Web应用构建。
37 3
|
2月前
|
C# 前端开发 UED
WPF数据验证实战:内置控件与自定义规则,带你玩转前端数据验证,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,数据验证是确保输入正确性的关键环节。前端验证能及时发现错误,提升用户体验和程序可靠性。本文对比了几种常用的WPF数据验证方法,并通过示例展示了如何使用内置验证控件(如`TextBox`)及自定义验证规则实现有效验证。内置控件结合`Validation`类可快速实现简单验证;自定义规则则提供了更灵活的复杂逻辑支持。希望本文能帮助开发者更好地进行WPF数据验证。
52 0
|
2月前
|
前端开发 Java Spring
Spring与Angular/React/Vue:当后端大佬遇上前端三杰,会擦出怎样的火花?一场技术的盛宴,你准备好了吗?
【8月更文挑战第31天】Spring框架与Angular、React、Vue等前端框架的集成是现代Web应用开发的核心。通过RESTful API、WebSocket及GraphQL等方式,Spring能与前端框架高效互动,提供快速且功能丰富的应用。RESTful API简单有效,适用于基本数据交互;WebSocket支持实时通信,适合聊天应用和数据监控;GraphQL则提供更精确的数据查询能力。开发者可根据需求选择合适的集成方式,提升用户体验和应用功能。
70 0
|
2月前
|
前端开发 Java UED
JSF遇上Material Design:一场视觉革命,如何让传统Java Web应用焕发新生?
【8月更文挑战第31天】在当前的Web开发领域,用户体验和界面美观性至关重要。Google推出的Material Design凭借其独特的动画、鲜艳的颜色和简洁的布局广受好评。将其应用于JavaServer Faces(JSF)项目,能显著提升应用的现代感和用户交互体验。本文介绍如何通过PrimeFaces等组件库在JSF应用中实现Material Design风格,包括添加依赖、使用组件及响应式布局等步骤,为用户提供美观且功能丰富的界面。
34 0
|
2月前
|
前端开发 大数据 数据库
🔥大数据洪流下的决战:JSF 表格组件如何做到毫秒级响应?揭秘背后的性能魔法!💪
【8月更文挑战第31天】在 Web 应用中,表格组件常用于展示和操作数据,但在大数据量下性能会成瓶颈。本文介绍在 JavaServer Faces(JSF)中优化表格组件的方法,包括数据处理、分页及懒加载等技术。通过后端分页或懒加载按需加载数据,减少不必要的数据加载和优化数据库查询,并利用缓存机制减少数据库访问次数,从而提高表格组件的响应速度和整体性能。掌握这些最佳实践对开发高性能 JSF 应用至关重要。
45 0
|
2月前
|
前端开发 API 开发者
JSF与RESTful服务的完美邂逅:如何打造符合现代Web潮流的数据交互新体验
【8月更文挑战第31天】随着互联网技术的发展,RESTful架构风格因其实现简便与无状态特性而在Web服务构建中日益流行。本文探讨如何结合JavaServer Faces (JSF) 和 JAX-RS 构建RESTful API,展示从前端到后端分离的完整解决方案。通过定义资源类、配置 `web.xml` 文件以及使用依赖注入等步骤,演示了在JSF项目中实现RESTful服务的具体过程,为Java开发者提供了实用指南。
32 0
|
2月前
|
开发者 C# C++
揭秘:如何轻松驾驭Uno Platform,用C#和XAML打造跨平台神器——一步步打造你的高性能WebAssembly应用!
【8月更文挑战第31天】Uno Platform 是一个跨平台应用程序框架,支持使用 C# 和 XAML 创建多平台应用,包括 Web。通过编译为 WebAssembly,Uno Platform 可实现在 Web 上运行高性能、接近原生体验的应用。本文介绍如何构建高效的 WebAssembly 应用:首先确保安装最新版本的 Visual Studio 或 VS Code 并配置 Uno Platform 开发环境;接着创建新的 Uno Platform 项目;然后通过安装工具链并使用 Uno WebAssembly CLI 编译应用;最后添加示例代码并测试应用。
55 0
|
2月前
|
iOS开发 Android开发 MacOS
从零到全能开发者:解锁Uno Platform,一键跨越多平台应用开发的神奇之旅,让你的代码飞遍Windows、iOS、Android、macOS及Web,技术小白也能秒变跨平台大神!
【8月更文挑战第31天】从零开始,踏上使用Uno Platform开发跨平台应用的旅程。只需编写一次代码,即可轻松部署到Windows、iOS、macOS、Android及Web(通过WASM)等多个平台。Uno Platform为.NET生态带来前所未有的灵活性和效率,简化跨平台开发。首先确保安装了Visual Studio或VS Code及.NET SDK,然后选择合适的项目模板创建新项目。项目结构类似传统.NET MAUI或WPF项目,包含核心NuGet包。通过简单的按钮示例,你可以快速上手并构建应用。Uno Platform让你的技术探索之旅充满无限可能。
38 0
下一篇
无影云桌面